Source: delite/FormValueWidget.js

/** @module delite/FormValueWidget */
define([
	"dcl/dcl",
	"./FormWidget",
	"./activationTracker"
], function (dcl, FormWidget) {

	/**
	 * Returns a method to set a new value and fire an event (change or input) if the value changed since the last
	 * call.  Widget should use `handleOnChange()` or `handleOnInput()`.
	 * @param {string} eventType - The event type. Can be "change" or "input".
	 * @param {string} prevValueProp - The name of the property to hold the previous value.
	 * @param {string} deferHandleProp - The name of the property to hold the defer method that fire the event.
	 * @returns {Function}
	 * @private
	 */
	function genHandler(eventType, prevValueProp, deferHandleProp) {
		// Set value and fire an input event if the value changed since the last call.
		// @param {*} newValue - The new value.
		return function (newValue) {
			this.value = newValue;

			// defer allows debounce, hidden value processing to run, and
			// also the onChange handler can safely adjust focus, etc.
			if (this[deferHandleProp]) {
				this[deferHandleProp].remove();
			}
			this[deferHandleProp] = this.defer(function () {
				delete this[deferHandleProp];
				if (typeof newValue !== typeof this[prevValueProp] ||
					this.compare(newValue, this[prevValueProp]) !== 0) { // ignore if value [eventually] set to orig val
					this[prevValueProp] = newValue;
					this.deliver();			// make sure rendering is in sync when event handlers are called
					this.emit(eventType);
				}
			});
		};
	}

	/**
	 * Base class intended for form widgets that have end user changeable values, i.e.
	 * widgets where the user can interactively change the value property by using the mouse, keyboard, touch, etc.
	 *
	 * FormValueWidget extends FormWidget to:
	 *
	 * 1. Provide helper functions to emit `change` and `input` events when the widget's value is interactively changed
	 *    by the end user.  Subclasses of FormValueWidget should call `handleOnChange()` and
	 *    `handleOnInput()` to fire `change` and `input` events as the value changes.  See
	 *    https://html.spec.whatwg.org/multipage/forms.html#common-input-element-events for details.
	 * 2. Provide handling for the `readOnly` property.
	 *
	 * @mixin module:delite/FormValueWidget
	 * @augments module:delite/FormWidget
	 */
	return dcl(FormWidget, /** @lends module:delite/FormValueWidget# */{
		/**
		 * If true, this widget won't respond to user input.  Similar to `disabled` except
		 * `readOnly` form values are submitted.  FormValueWidget automatically updates
		 * `focusNode`'s `readOnly` property to match the widget's `readOnly` property.
		 * @member {boolean}
		 * @default false
		 */
		readOnly: false,

		refreshRendering: function (oldValues) {
			if ("tabStops" in oldValues || "readOnly" in oldValues) {
				this.forEachFocusNode(function (node) {
					node.readOnly = this.readOnly;
				});
			}
		},

		/**
		 * Compare two values (of this widget).
		 * @param {*} val1
		 * @param {*} val2
		 * @returns {number}
		 * @protected
		 */
		compare: function (val1, val2) {
			if (typeof val1 === "number" && typeof val2 === "number") {
				return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
			} else if (val1 > val2) {
				return 1;
			} else if (val1 < val2) {
				return -1;
			} else {
				return 0;
			}
		},

		createdCallback: function () {
			this.on("delite-activated", function () {
				// Called when user may be about to start input.
				// Saves the widget's current value, which is the most recent of:
				//
				//	1. the original value set on widget construction
				//	2. the value the user set when he previously used the widget
				//	3. the value the application set programatically
				//
				// This is all to avoid firing unnecessary change/input events in the corner case where the
				// user just selects and releases the Slider handle for example.
				this._previousOnChangeValue = this.value;
				this._previousOnInputValue = this.value;
			});
		},

		/**
		 * Sets value and fires a "change" event if the value changed since the last call.
		 *
		 * This method should be called when the value is committed,
		 * if that makes sense for the control, or else when the control loses focus.
		 * For example, it should be called when the user releases a slider's handle after dragging it,
		 * or when the user blurs a textbox.
		 * See https://html.spec.whatwg.org/multipage/forms.html#common-input-element-events for details.
		 *
		 * @param {*} newValue - The new value.
		 * @function
		 * @protected
		 */
		handleOnChange: genHandler("change", "_previousOnChangeValue", "_onChangeHandle"),

		/**
		 * Sets value and fires an "input" event if the value changed since the last call.
		 *
		 * This method should be called whenever the value is changed interactively by the end user.
		 * For example, it should be called repeatedly as the user drags the handle of a slider,
		 * or on every keystroke for a textbox.
		 * See https://html.spec.whatwg.org/multipage/forms.html#common-input-element-events for details.
		 *
		 * @param {*} newValue - The new value.
		 * @function
		 * @protected
		 */
		handleOnInput: genHandler("input", "_previousOnInputValue", "_onInputHandle"),

		afterFormResetCallback: function () {
			console.log(this.id, "FormValueWidget#afterFormResetCallback");
			if (this.value !== this.valueNode.value) {
				this.value = this.valueNode.value;
			}
		}
	});
});