Source RapidContext_Widget_Field.js

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