/*
* This file is part of AUX.
*
* AUX is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* AUX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
import { Widget } from './widget.js';
import { element, addClass, removeClass } from './../utils/dom.js';
import { defineRender } from '../renderer.js';
const SymEditing = Symbol('__editing changed');
/**
* The <code>useraction</code> event is emitted when a widget gets modified by user interaction.
* The event is emitted for the option <code>value</code>.
*
* @event Value#useraction
*
* @param {string} name - The name of the option which was changed due to the users action
* @param {mixed} value - The new value of the option
*/
function valueClicked() {
const O = this.options;
if (O.set === false) return;
if (this.__editing) return false;
this._input.setAttribute('value', O.value);
this._input.focus();
/**
* Is fired when the value was clicked.
*
* @event Value#valueclicked
*
* @param {number} value - The value of the widget.
*/
this.emit('valueclicked', O.value);
}
function valueKeydown(e) {
const O = this.options;
switch (e.keyCode) {
case 9:
// TAB
if (!O.tab_to_set) return;
this.userset(
'value',
O.set ? O.set.call(this, this._input.value) : this._input.value
);
valueDone.call(this, true);
this.emit('valueset', O.value);
return false;
}
}
function valueTyping(e) {
const O = this.options;
if (O.set === false) return;
if (!this.__editing) return;
switch (e.keyCode) {
case 27:
// ESC
valueDone.call(this);
/**
* Is fired when the ESC key was pressed while editing the value.
*
* @event Value#valueescape
*
* @param {string} value - The new value of the widget.
*/
this.emit('valueescape', O.value);
break;
case 13:
// ENTER
this.userset(
'value',
O.set ? O.set.call(this, this._input.value) : this._input.value
);
valueDone.call(this);
/**
* Is fired after the value has been set and editing has ended.
*
* @event Value#valueset
*
* @param {string} value - The new value of the widget.
*/
this.emit('valueset', O.value);
e.preventDefault();
return false;
}
/**
* Is fired when the user hits a key while editing the value.
*
* @event Value#valuetyping
*
* @param {DOMEvent} event - The native DOM event.
* @param {string} value - The new value of the widget.
*/
this.emit('valuetyping', e, O.value);
}
function valueInput() {
const O = this.options;
if (O.set === false) return;
if (!this.__editing) return;
if (O.editmode === 'immediate')
this.userset(
'value',
O.set ? O.set.call(this, this._input.value) : this._input.value
);
}
function valueDone(noblur) {
if (!this.__editing) return;
this.__editing = false;
this.invalidate(SymEditing);
removeClass(this.element, 'aux-active');
if (!noblur) this._input.blur();
/**
* Is fired when editing of the value ends.
*
* @event Value#valuedone
*
* @param {string} value - The new value of the widget.
*/
this.emit('valuedone', this.options.value);
this.stopInteracting();
}
function valueFocus() {
const O = this.options;
this.__editing = true;
this.invalidate(SymEditing);
addClass(this.element, 'aux-active');
setTimeout(() => {
if (O.auto_select)
this._input.setSelectionRange(0, this._input.value.length);
}, 10);
this.startInteracting();
}
function submitCallback(e) {
e.preventDefault();
return false;
}
/**
* Value is a formatted and editable text field displaying values as
* strings or numbers.
*
* @class Value
*
* @extends Widget
*
* @param {Object} [options={ }] - An object containing initial options.
*
* @property {Number} [options.value=0] - The value.
* @property {Function} [options.format=function (v) { return v; }] - A formatting
* function used to display the value.
* @property {Null|Integer} [options.size=1] - Size attribute of the input element. `null` to unset.
* @property {Null|Integer} [options.maxlength=null] - Maxlength attribute of the input element. `null` to unset.
* @property {Function} [options.set=function (val) { return parseFloat(val || 0); }] -
* A function which is called to parse user input.
* @property {Boolean} [options.auto_select=false] - Select the entire text if clicked .
* @property {Boolean} [options.readonly=false] - Sets the readonly attribute.
* @property {String} [options.placeholder=""] - Sets the placeholder attribute.
* @property {String} [options.type="text"] - Sets the type attribute. Type can be either `text` or `password`.
* @property {String} [options.editmode="onenter"] - Sets the event to trigger the userset event. Can be one out of `onenter` or `immediate`.
* @property {String|Boolean} [options.autocomplete=false} - Set a unique identifier to enable browsers internal auto completion.
* @property {Boolean} [options.tab_to_set=false} - Set the value if user hits TAB.
*/
export class Value extends Widget {
static get _options() {
return {
value: 'number|string',
format: 'function',
size: 'number',
maxlength: 'int',
set: 'object|function|boolean',
auto_select: 'boolean',
readonly: 'boolean',
placeholder: 'string',
type: 'string',
editmode: 'string',
autocomplete: 'string|boolean',
tab_to_set: 'boolean',
};
}
static get options() {
return {
value: 0,
format: function (v) {
return v;
},
size: 3,
maxlength: null,
container: false,
// set a callback function if value is editable or
// false to disable editing. A function has to return
// the value treated by the parent widget.
set: function (val) {
val = parseFloat(val);
if (isNaN(val)) return this.get('value');
return parseFloat(val || 0);
},
auto_select: true,
readonly: false,
placeholder: '',
type: 'text',
editmode: 'onenter',
autocomplete: false,
tab_to_set: false,
role: 'textbox',
};
}
static get renderers() {
return [
defineRender('size', function (size) {
this._input.setAttribute('size', size);
}),
defineRender('maxlength', function (maxlength) {
const E = this._input;
if (maxlength) E.setAttribute('maxlength', maxlength);
else E.removeAttribute('maxlength');
}),
defineRender('placeholder', function (placeholder) {
const E = this._input;
if (placeholder) {
E.setAttribute('placeholder', placeholder);
E.setAttribute('aria-placeholder', placeholder);
} else {
E.removeAttribute('placeholder');
E.removeAttribute('aria-placeholder');
}
}),
defineRender(['value', 'format', SymEditing], function (value, format) {
if (this.__editing) return;
this._input.value = format(value);
}),
defineRender('readonly', function (readonly) {
const E = this._input;
if (readonly) E.setAttribute('readonly', 'readonly');
else E.removeAttribute('readonly');
}),
defineRender('type', function (type) {
this._input.setAttribute('type', type);
}),
defineRender('autocomplete', function (autocomplete) {
const E = this._input;
if (autocomplete) {
E.setAttribute('name', autocomplete);
E.setAttribute('autocomplete', 'on');
} else {
E.removeAttribute('name');
E.removeAttribute('autocomplete');
}
}),
];
}
initialize(options) {
if (!options.element) options.element = element('div');
/**
* @member {HTMLDivElement} Value#element - The main DIV container.
* Has class <code>aux-value</code>.
*/
super.initialize(options);
/**
* @member {HTMLInputElement} Value#_input - The input element.
* Has class <code>aux-input</code>.
*/
this._input = element('input');
this._value_typing = valueTyping.bind(this);
this._value_keydown = valueKeydown.bind(this);
this._value_done = valueDone.bind(this);
this._value_input = valueInput.bind(this);
this._value_clicked = valueClicked.bind(this);
this._value_focus = valueFocus.bind(this);
this.__editing = false;
}
getEventTarget() {
return this._input;
}
getFocusTargets() {
return [this._input];
}
getRoleTarget() {
return this._input;
}
draw(O, elmnt) {
addClass(elmnt, 'aux-value');
addClass(this._input, 'aux-input');
this.element.appendChild(this._input);
this._input.addEventListener('keyup', this._value_typing);
this._input.addEventListener('keydown', this._value_keydown);
this._input.addEventListener('input', this._value_input);
this._input.addEventListener('blur', this._value_done);
this._input.addEventListener('focus', this._value_focus);
this._input.addEventListener('click', this._value_clicked);
this._input.addEventListener('submit', submitCallback);
super.draw(O, elmnt);
}
destroy() {
this._input.removeEventListener('keyup', this._value_typing);
this._input.removeEventListener('keydown', this._value_keydown);
this._input.removeEventListener('blur', this._value_done);
this._input.removeEventListener('input', this._value_input);
this._input.removeEventListener('focus', this._value_focus);
this._input.removeEventListener('click', this._value_clicked);
this._input.removeEventListener('submit', submitCallback);
this._input.remove();
super.destroy();
}
}