UNPKG

toloframework

Version:

Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.

280 lines (264 loc) 8.69 kB
var $ = require("dom"); var DB = require("tfw.data-binding"); var LaterAction = require("tfw.timer").laterAction; /** * @class tfw.edit.text * @description HTML5 text input with many options. * * __Attributes__: * * {string} `value`: * * {string} `value`: * * {string} `value`: * * {string} `value`: * * {string} `value`: * * {string} `value`: * * {string} `value`: */ var Text = function(opts) { var that = this; var dataListHasFocus = false; var autocompleteProposals = []; var autocompleteShift = 0; var label = $.div( 'theme-label', 'theme-color-bg-1' ); var input = $.tag( 'input' ); var datalist = $.div( 'datalist', 'theme-elevation-12' ); this._input = input; var elem = $.elem( this, 'div', 'wdg-text', 'theme-elevation-2', [label, input, datalist] ); DB.propString(this, 'value')(function(v) { input.value = v; that.validate(); }); DB.propEnum(['text', 'button', 'checkbox', 'color', 'date', 'datetime', 'email', 'file', 'hidden', 'image', 'month', 'password', 'radio', 'range', 'reset', 'search', 'submit', 'tel', 'time', 'url', 'week'])(this, 'type') (function(v) { $.att(input, {type: v}); }); DB.propStringArray(this, 'list')(function(v) { $.clear( datalist ); $.removeClass( elem, "list" ); if (!Array.isArray( v )) return; v.forEach(function ( item ) { $.add( datalist, $.div( [item] ) ); }); if( v.length > 0 ) { $.att( elem, "list" ); } }); DB.propValidator(this, 'validator')(this.validate.bind( this )); DB.propBoolean(this, 'valid')(function(v) { if (v === null || !that.validator) { $.removeClass( elem, "valid", "no-valid" ); } else { if (v) { $.addClass( elem, "valid" ); $.removeClass( elem, "no-valid" ); } else { $.removeClass( elem, "valid" ); $.addClass( elem, "no-valid" ); } } }); DB.propBoolean(this, 'enabled')(function(v) { if (v) { $.removeAtt(input, 'disabled'); } else { $.att(input, {disabled: v}); } }); DB.propInteger(this, 'size')(function(v) { if (v < 1) { $.removeAtt(input, 'size'); } else { $.att(input, {size: v}); } }); DB.propString(this, 'label')(function(v) { if (v === null || (typeof v === 'string' && v.trim() == '')) { $.addClass(elem, 'no-label'); } else { $.removeClass(elem, 'no-label'); $.textOrHtml(label, v); if (v.substr(0, 6) == '<html>') { $.att(label, {title: ''}); } else { $.att(label, {title: v}); } } }); DB.propString(this, 'placeholder')(function(v) { $.att(input, {placeholder: v}); }); DB.propString(this, 'width')(function(v) { elem.style.width = v; }); DB.propBoolean(this, 'focus')(function(v) { if (v) input.focus(); else input.blur(); }); DB.propInteger(this, 'action', ''); DB.propAddClass(this, 'wide'); DB.propRemoveClass(this, 'visible', 'hide'); opts = DB.extend({ value: '', type: 'text', placeholder: '', enabled: true, validator: null, valid: true, list: null, label: '', placeholder: '', size: 10, width: 'auto', focus: false, wide: false, visible: true }, opts, this); var complete = function() { $.removeClass( elem, "list" ); if (!that.list || that.list.length == 0) return; $.clear( datalist ); var list = that.list.map(String.toLowerCase); var needle = input.value.trim().toLowerCase(); if (needle.length > 0) { list = list.map(function(itm, idx) { return [idx, itm.indexOf( needle )]; }).filter(function(itm) { return itm[1] > -1; }).sort(function(a, b) { var d = a[1] - b[1]; if (d != 0) return d; var sa = that.list[a[0]]; var sb = that.list[b[0]]; if (sa < sb) return -1; if (sa > sb) return 1; return 0; }).map(function(itm) { var t = that.list[itm[0]]; var i = itm[1]; return t.substr(0, i) + "<b>" + t.substr(i, needle.length) + "</b>" + t.substr(i + needle.length); }); } else { list = list.sort(); } if (autocompleteShift > 0) { // Put the current item to the top of the list. // Use arrow keys to change `autocompleteShift`. list = list.slice( autocompleteShift ).concat( list.slice( 0, autocompleteShift ) ); } autocompleteProposals = list; list.forEach(function (item, idx) { var div = $.div(); div.innerHTML = item; list[idx] = div.textContent.trim(); $.add( datalist, div ); $.on( div, { down: function() { dataListHasFocus = true; }, up: function() { dataListHasFocus = false; that.focus = true; }, tap: function() { that.value = div.textContent.trim(); console.info("[wdg.text] div=...", div); $.removeClass( elem, 'list' ); } }); }); if (list.length > 0) { $.addClass( elem, "list" ); } else { $.removeClass( elem, "list" ); } }; var actionUpdateValue = LaterAction(function() { that.value = input.value; }, 300); input.addEventListener('keyup', function(evt) { if (evt.keyCode == 13) { evt.preventDefault(); evt.stopPropagation(); if ($.hasClass( elem, 'list' )) { $.removeClass( elem, 'list' ); that.value = autocompleteProposals[0]; } else if (that.valid !== false) { DB.fire( that, 'value', input.value ); DB.fire( that, 'action', input.value ); } } else if (evt.keyCode == 27) { $.removeClass( elem, "list" ); autocompleteShift = 0; evt.preventDefault(); evt.stopPropagation(); } else if (evt.keyCode == 40 && $.hasClass( elem, 'list' )) { autocompleteShift = (autocompleteShift + 1) % autocompleteProposals.length; complete(); evt.preventDefault(); evt.stopPropagation(); } else if (evt.keyCode == 38 && $.hasClass( elem, 'list' )) { autocompleteShift = (autocompleteShift + autocompleteProposals.length - 1) % autocompleteProposals.length; complete(); evt.preventDefault(); evt.stopPropagation(); } else { autocompleteShift = 0; complete(); actionUpdateValue.fire(); } }); input.addEventListener('blur', function() { that.value = input.value; if (!dataListHasFocus) { $.removeClass( elem, "list" ); } $.addClass( elem, "theme-elevation-2" ); $.removeClass( elem, "theme-elevation-8" ); $.removeClass(input, 'theme-color-bg-A1'); that.focus = false; }); input.addEventListener('focus', function() { that.selectAll(); $.removeClass( elem, "theme-elevation-2" ); $.addClass( elem, "theme-elevation-8" ); $.addClass(input, 'theme-color-bg-A1'); that.focus = true; }); input.addEventListener('keydown', function(evt) { }); this.validate(); }; /** * Force value validation. */ Text.prototype.validate = function() { var validator = this.validator; if (!validator) return; try { this.valid = validator( this.value ); } catch (ex) { console.error("[wdg.text:validate] Exception = ", ex); console.error("[wdg.text:validate] Validator = ", validator); } }; /** * Select whole text. * @return {this} */ Text.prototype.selectAll = function() { var e = this._input; e.setSelectionRange(0, e.value.length); return true; }; module.exports = Text;