Source RapidContext_Widget_TextArea.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 text area (or text box) 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 field value, defaults to ""
28 * @param {string} [attrs.helpText] the help text when empty (deprecated)
29 * @param {boolean} [attrs.autosize] the auto-resize flag, defaults to false
30 * @param {boolean} [attrs.disabled] the disabled widget flag, defaults to
31 *            false
32 * @param {boolean} [attrs.hidden] the hidden widget flag, defaults to false
33 * @param {...(string|Node)} [value] the initial text content
34 *
35 * @return {Widget} the widget DOM node
36 *
37 * @class The text area widget class. Used to provide a text input field
38 *     spanning multiple rows, using the `<textarea>` HTML element.
39 * @property {boolean} disabled The read-only widget disabled flag.
40 * @property {string} defaultValue The value to use on form reset.
41 * @extends RapidContext.Widget
42 *
43 * @example <caption>JavaScript</caption>
44 * var attrs = { name="description", placeholder: "Description Text" };
45 * var field = RapidContext.Widget.TextArea(attrs);
46 *
47 * @example <caption>User Interface XML</caption>
48 * <TextArea name="description" placeholder="Description Text" />
49 */
50RapidContext.Widget.TextArea = function (attrs/*, ...*/) {
51    function scrape(val) {
52        return String(val && val.textContent || val || "");
53    }
54    var text = (attrs && attrs.value) || "";
55    text += Array.from(arguments).slice(1).map(scrape).join("");
56    var o = MochiKit.DOM.TEXTAREA({ value: text });
57    RapidContext.Widget._widgetMixin(o, RapidContext.Widget.TextArea);
58    o.addClass("widgetTextArea");
59    o.resizeContent = o._resizeContent;
60    o.setAttrs(Object.assign({}, attrs, { value: text }));
61    o.on("input", o._handleChange);
62    return o;
63};
64
65// Register widget class
66RapidContext.Widget.Classes.TextArea = RapidContext.Widget.TextArea;
67
68/**
69 * Emitted when the text is modified. This event is triggered by either
70 * user events (keypress, paste, cut, blur) or by setting the value via
71 * setAttrs(). The DOM standard onchange event has no 'event.detail'
72 * data and is triggered on blur. The synthetic onchange events all
73 * contain an 'event.detail' object with 'before', 'after' and 'cause'
74 * properties.
75 *
76 * @name RapidContext.Widget.TextArea#onchange
77 * @event
78 */
79
80/**
81 * Updates the widget or HTML DOM node attributes.
82 *
83 * @param {Object} attrs the widget and node attributes to set
84 * @param {string} [attrs.name] the form field name
85 * @param {string} [attrs.value] the field value
86 * @param {string} [attrs.helpText] the help text when empty (deprecated)
87 * @param {boolean} [attrs.autosize] the auto-resize flag
88 * @param {boolean} [attrs.disabled] the disabled widget flag
89 * @param {boolean} [attrs.hidden] the hidden widget flag
90 */
91RapidContext.Widget.TextArea.prototype.setAttrs = function (attrs) {
92    attrs = Object.assign({}, attrs);
93    if ("helpText" in attrs) {
94        console.warn("deprecated: setting 'helpText' attribute, use 'placeholder' instead");
95        attrs.placeholder = attrs.placeholder || attrs.helpText;
96        delete attrs.helpText;
97    }
98    if ("value" in attrs) {
99        // FIXME: This is wrong, since we're setting an attribute here.
100        // But until Form.update() has some other way to set a field
101        // value and trigger changes, this will remain.
102        this.value = attrs.value || "";
103        this._handleChange(null);
104        delete attrs.value;
105    }
106    this.__setAttrs(attrs);
107};
108
109/**
110 * Resets the text area form value to the initial value.
111 */
112RapidContext.Widget.TextArea.prototype.reset = function () {
113    this.setAttrs({ value: this.defaultValue });
114};
115
116/**
117 * Returns the text area value. This function is slightly different from using
118 * the `value` property directly, since it will attempt to normalize newlines
119 * in the value.
120 *
121 * @return {string} the field value
122 *
123 * @example
124 * var str = field.getValue();
125 * var lines = str.split("\n").map((s) => s.trim());
126 * field.setAttrs({ "value": lines.join("\n") });
127 */
128RapidContext.Widget.TextArea.prototype.getValue = function () {
129    var str = this.value;
130    // This is a hack to remove multiple newlines caused by
131    // platforms inserting or failing to normalize newlines
132    // within the HTML textarea control.
133    str = str.replace(/\r\n\n/g, "\n");
134    if (this.value != str) {
135        this.value = str;
136    }
137    return str;
138};
139
140/**
141 * Handles input events for this this widget.
142 *
143 * @param {Event} [evt] the DOM Event object or null for manual
144 */
145RapidContext.Widget.TextArea.prototype._handleChange = function (evt) {
146    var cause = (evt && evt.inputType) || "set";
147    var detail = { before: this.storedValue || "", after: this.value, cause: cause };
148    this.emit("change", { detail: detail, bubbles: true });
149    this.storedValue = this.value;
150    this._resizeContent();
151};
152
153/**
154 * Resizes the text area if auto-sized. This method is automatically
155 * called when a dialog is shown (or resized), and is also called if
156 * the input is modified.
157 */
158RapidContext.Widget.TextArea.prototype._resizeContent = function () {
159    if (this.autosize) {
160        this.style.height = "auto";
161        if (this.scrollHeight > 10) {
162            this.style.height = this.scrollHeight + "px";
163        }
164    }
165};
166