Source RapidContext_Widget_Field.js

1/*
2 * RapidContext <https://www.rapidcontext.com/>
3 * Copyright (c) 2007-2023 Per Cederberg. All rights reserved.
4 *
5 * This program is free software: you can redistribute it and/or
6 * modify it under the terms of the BSD license.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11 *
12 * See the RapidContext LICENSE for more details.
13 */
14
15// Namespace initialization
16if (typeof(RapidContext) == "undefined") {
17    RapidContext = {};
18}
19RapidContext.Widget = RapidContext.Widget || { Classes: {} };
20
21/**
22 * Creates a new field widget.
23 *
24 * @constructor
25 * @param {Object} attrs the widget and node attributes
26 * @param {string} attrs.name the form field name
27 * @param {string} [attrs.value] the initial field value, defaults
28 *            to an empty string
29 * @param {string} [attrs.format] the field format string, defaults
30 *            to "{:s}"
31 * @param {function} [attrs.formatter] the value formatter function
32 * @param {number} [attrs.maxLength] the maximum data length,
33 *            overflow will be displayed as a tooltip, defaults to
34 *            unlimited
35 * @param {boolean} [attrs.mask] the masked display flag, when set
36 *            the field value is only displayed after the user has
37 *            clicked the field, defaults to false
38 * @param {boolean} [attrs.hidden] the hidden widget flag, defaults to false
39 *
40 * @return {Widget} the widget DOM node
41 *
42 * @class The field widget class. This widget is useful for providing
43 *     visible display of form data, using a `<span>` HTML element.
44 * @extends RapidContext.Widget
45 *
46 * @example <caption>JavaScript</caption>
47 * let attrs = { name: "ratio", value: 0.23, format: "Ratio: {:%}" };
48 * let field = RapidContext.Widget.Field(attrs);
49 *
50 * @example <caption>User Interface XML</caption>
51 * <Field name="ratio" value="0.23" format="Ratio: {:%}" />
52 */
53RapidContext.Widget.Field = function (attrs) {
54    let o = document.createElement("span");
55    RapidContext.Widget._widgetMixin(o, RapidContext.Widget.Field);
56    o.addClass("widgetField");
57    o.setAttrs(Object.assign({ name: "", value: "" }, attrs));
58    o.defaultValue = o.value;
59    o.defaultMask = !!o.mask;
60    o.on("click", o._handleClick);
61    return o;
62};
63
64// Register widget class
65RapidContext.Widget.Classes.Field = RapidContext.Widget.Field;
66
67/**
68 * Returns the widget container DOM node.
69 *
70 * @return {Node} returns null, since child nodes are not supported
71 */
72RapidContext.Widget.Field.prototype._containerNode = function () {
73    return null;
74};
75
76/**
77 * Updates the widget or HTML DOM node attributes.
78 *
79 * @param {Object} attrs the widget and node attributes to set
80 * @param {string} [attrs.name] the form field name
81 * @param {string} [attrs.value] the field value
82 * @param {string} [attrs.format] the field format string
83 * @param {function} [attrs.formatter] the value formatter function
84 * @param {number} [attrs.maxLength] the maximum data length,
85 *            overflow will be displayed as a tooltip
86 * @param {boolean} [attrs.mask] the masked display flag, when set
87 *            the field value is only displayed after the user has
88 *            clicked the field
89 * @param {boolean} [attrs.hidden] the hidden widget flag
90 *
91 * @example
92 * field.setAttrs({ value: 0.23 });
93 */
94RapidContext.Widget.Field.prototype.setAttrs = function (attrs) {
95    attrs = Object.assign({}, attrs);
96    if ("formatter" in attrs) {
97        let valid = typeof(attrs.formatter) == "function";
98        attrs.formatter = valid ? attrs.formatter : null;
99    }
100    if ("maxLength" in attrs) {
101        let val = parseInt(attrs.maxLength, 10);
102        attrs.maxLength = isNaN(val) ? null : val;
103    }
104    if ("mask" in attrs) {
105        attrs.mask = RapidContext.Data.bool(attrs.mask);
106    }
107    this.__setAttrs(attrs);
108    this.redraw();
109};
110
111/**
112 * Redraws the field from updated values or status. Note that this
113 * method is called automatically whenever the `setAttrs()` method is
114 * called.
115 */
116RapidContext.Widget.Field.prototype.redraw = function () {
117    let str = this.value;
118    if (this.formatter) {
119        try {
120            str = this.formatter(str);
121        } catch (e) {
122            str = e.message;
123        }
124    } else if (str == null || str === "") {
125        str = "";
126    } else if (this.format) {
127        str = MochiKit.Text.format(this.format, str);
128    } else if (typeof(str) != "string") {
129        str = str.toString();
130    }
131    let longStr = str;
132    if (this.maxLength > 0) {
133        str = MochiKit.Text.truncate(str, this.maxLength, "...");
134    }
135    if (this.mask) {
136        this.addClass("widgetFieldMask");
137        this.title = "Click to show";
138        this.innerText = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
139        this.append(RapidContext.Widget.Icon({ ref: "LOCK", tooltip: "Click to show" }));
140    } else {
141        if (str == longStr) {
142            delete this.title;
143        } else {
144            this.title = longStr;
145        }
146        this.innerText = str;
147    }
148};
149
150/**
151 * Resets the field value to the initial value.
152 */
153RapidContext.Widget.Field.prototype.reset = function () {
154    this.setAttrs({ value: this.defaultValue, mask: this.defaultMask });
155};
156
157/**
158 * Handles click events on the field (if masked).
159 */
160RapidContext.Widget.Field.prototype._handleClick = function () {
161    if (this.mask) {
162        this.mask = false;
163        this.removeClass("widgetFieldMask");
164        this.redraw();
165    }
166};
167