UNPKG

toloframework

Version:

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

479 lines (449 loc) 16.1 kB
/** * Tous les générateurs de wtags héritent de cette classe qui contient * des fonctions utiles communes. * * @class WTag * @namespace WTag */ function N(a) { var t = typeof a; if (t === 'number') return a; if (t === 'undefined') return 0; if (t === 'null') return 0; try { var v = parseFloat(a); if (isNaN(v)) return 0; return v; } catch (e) { return 0; } } function S(a) { return "" + a; } window["TFW::WTag"] = { classInit: function(vars) { $$("dom.Util"); vars.globalSlots = {}; }, /** * @constructs WTag */ init: function() { // Store here all the stuff needed to make a cleanup when method `destroy` is called. this._widgetCleanUp = { signals: [], globalSignals: [], dataBindings: [] }; var element; if (typeof this._id === 'object') { element = this._id; } else { element = document.getElementById(this._id); } if (!element) { throw new Error("There is no element with id \"" + this._id + "\"!"); } element.$widget = this; this._element = element; this._slots = {}; // Look for binding expressions. // See in widgets the function Util.bindable(...). var key, attName; for (key in this) { if (key.substr(0, 3) == '_G$') { // There is a getter for data binding. attName = key.substr(3); this["_V$" + attName].forEach( function(dataName) { this.bindData(dataName, attName, this[key]); }, this ); this[attName].call(this, this[key].call(this)); } } }, functions: { /** * Fire a "signal" up to the parents widgets. * If a slot returns false, the event is fired up to the parents. * * @param signal Name of the signal to trigger. * @param arg Optional argument to sent with this signal. * @param emitter Optional reference to the signal's emitter. * @memberof WTag */ fire: function(signal, arg, emitter) { var widget = this, slot; if (typeof emitter === 'undefined') emitter = this; console.log("fire(" + signal + ")", arg); if (signal.charAt(0) == '@') { // This is a global signal. slot = $$.statics("WTag"); if (slot) { slot = slot.globalSlots[signal]; if (slot) { slot[1].call(slot[0], arg, signal, emitter); } else { console.error( "[WTag.fire] Nothing is binded on global signal \"" + signal + "\"!" ); } } } if (signal.charAt(0) == '$') { // Assign a value to a data. this.data(signal.substr(1).trim(), arg); } else { while (widget) { slot = widget._slots[signal]; if (typeof slot === 'function') { if (false !== slot.call(widget, arg, signal, emitter)) { return; } } widget = widget.parentWidget(); } console.warning("Signal lost: " + signal + "!"); } }, /** * Register a listener (the function "slot") for the signal * "signal". If this slot returns true, the signal continue * its ascension towards parent widgets. * * @param signal Name of the signal to catch. * @param slot Function to call to process this signal * @memberof WTag * @inner * @memberof WTag */ registerSignal: function(signal, slot) { if (signal.charAt(0) == '@') { // Registering a global signal. $$.statics("WTag").globalSlots[signal] = [this, slot]; } else { this._slots[signal] = slot; } }, /** * Stop listening for the "signal". * @memberof WTag */ unregisterSignal: function(signal) { delete this._slots[signal]; }, /** * Call the slot mapped to the "signal". * @param signal : name of the signal on which this object may be registred. * @param arg : argument to pass to the registred slot. * @memberof WTag */ slot: function(signal, arg) { var slot = this._slots[signal]; if (slot) { slot.call(this, arg, signal); return true; } return false; }, /** * Get the parent element. * @memberof WTag */ parentElement: function() { return this._element.parentNode; }, /** * Get the parent widget. * @memberof WTag */ parentWidget: function() { var element = this._element; while (element.parentNode) { element = element.parentNode; if (element.$widget) { return element.$widget; } } return null; }, /** * Return or set the current language. * @memberof WTag */ lang: function(id) { if (!$$.App) $$.App = this; if (!$$.App._languages) { // Initialise localization. var languages = [], langStyle = document.createElement("style"), children = document.querySelectorAll("[lang]"); document.head.appendChild(langStyle); $$.App._langStyle = langStyle; for (var i = 0 ; i < children.length ; i++) { var child = children[i]; lang = child.getAttribute("lang"); found = false; for (k = 0 ; k < languages.length ; k++) { if (languages[k] == lang) { found = true; break; } } if (!found) { languages.push(lang); } } $$.App._languages = languages; } var that = this, lang, k, found, first, txt; languages = $$.App._languages; if (id === undefined) { // Return current language. lang = $$.lang(); // localStorage.getItem("wtag-language"); if (!lang) { lang = navigator.language || navigator.browserLanguage || "fr"; lang = lang.substr(0, 2); } $$.lang(lang); // localStorage.setItem("wtag-language", lang); return lang; } else { // Set current language and display localized elements. found = false; for (k = 0 ; k < languages.length ; k++) { if (languages[k] == id) { found = true; break; } } if (!found) { id = languages[0]; } txt = ""; first = true; for (k = 0 ; k < languages.length ; k++) { lang = languages[k]; if (lang != id) { if (first) { first = false; } else { txt += ","; } txt += "[lang=" + lang + "]"; } } $$.App._langStyle.textContent = txt + "{display: none}"; $$.lang(id); //localStorage.setItem("wtag-language", id); } }, /** * Get an element with this `name` among this element's * children. * @memberof WTag */ findElement: function(name) { if (typeof name === 'undefined') return this._element; var e = this._element.querySelector("[name='" + name + "']"); if (!e) { throw new Error( "[WTag.get] Can't find child [name=\"" + name + "\"] in element \"" + this._id + "\"!" ); } return e; }, /** * Get the widget mapped to the element with this `name` among * this element's children. * @memberof WTag */ findWidget: function(name) { if (typeof name === 'undefined') return this; var element = this.findElement(name); if (element) { return element.$widget; } return null; }, /** * @description * Databindings are scoped and stored in the `$data` property of a DOM element. * We always take data from the nearest parent element. * * @param {string} name name of the data binding. * @return object representing data bindings. The key is the * name of the data and the value is a two-items array. First * item is the current value for this data, and second item is a * list of all listeners. * Each listener is an object with these attributes: * * `obj`: the real listener object. * * `slot`: the method of `obj` to call with a value as unique argument, when datat has changed. * * `getter`: the method of `obj` to call in order to get the value to pass to `slot`. * @memberof WTag */ findDataBinding: function(name) { var data, dataOwner, parent = this.findElement(); while (parent) { dataOwner = parent; if (dataOwner.$data && name in dataOwner.$data) { break; } if (dataOwner.nodeName.toLowerCase() == 'html') { break; } parent = dataOwner.parentNode; } data = dataOwner.$data[name]; if (typeof data === 'undefined') { data = ["", []]; dataOwner.$data[name] = data; } return data; }, /** * Set/Get bindable data. * @memberof WTag */ data: function(name, value) { var data = this.findDataBinding(name); if (typeof value === 'undefined') { return data[0]; } if (value !== data[0]) { data[0] = value; this.fireData(data); } }, /** * @description * Simulate a data change. * @param {string|object} name name of the data, or the data binding object itself. * @memberof WTag */ fireData: function(name) { var data = name; if (typeof name === 'string') { data = this.findDataBinding(name); } data[1].forEach( function(listener) { var obj = listener.obj; var slot = listener.slot; var getter = listener.getter; if (listener !== this) { var value = getter.call(obj); slot.call(obj, value); } } ); }, /** * @description * Define a local data binding. * @param {string} name Name of this data. * @param value Initial value. * @memberof WTag */ defineLocalData: function(name, value) { var e = this.findElement(); if (!this._dataBinding) { e.$data = {}; } e.$data[name] = [value, []]; }, /** * Bind to data updates. * When the data changed, the `slot` is call with `this` object * and the data's value as unique argument. * @param {array} vars array of names of data to watch. * @param {function} slot function to call when data changed. * @param {string} slot name of the method to call when data changed. * @param {function} getter function getting the binded value. * @return {object} the listener you can give to `unbindData`. * @memberof WTag */ bindData: function(vars, slot, getter) { if (!Array.isArray(vars)) { vars = [vars]; } if (vars.length == 0) return null; if (typeof slot === 'string') { slot = this[slot]; } if (typeof getter === 'undefined') { getter = function() { return this.data(vars[0]); }; } var listener = { obj: this, slot: slot, getter: getter }; vars.forEach( function(name) { var data = this.findDataBinding(name); data[1].push(listener); }, this ); return listener; }, /** * Detach this object from data binding. * * @param {array} vars array of names of data to watch. * @param {object} listner the listener to remove. * @memberof WTag */ unbindData: function(vars, listener) { if (!Array.isArray(vars)) { vars = [vars]; } if (vars.length == 0) return null; var found = false; vars.forEach( function(name) { var data = this.findDataBinding(name); var i, target; for (i = 0 ; i < data[1].length ; i++) { target = data[1][i]; if (listener === target) { data[1].splice(i, 1); found = true; return; } } }, this ); return found; }, /** * @description * Remove all bindings. * @memberof WTag */ destroy: function() { }, ADD: function(a,b) {return N(a)+N(b);}, SUB: function(a,b) {return N(a)-N(b);}, MUL: function(a,b) {return N(a)*N(b);}, POW: function(a,b) {return Math.pow(N(a),N(b));}, DIV: function(a,b) {b=N(b); return b==0?0:N(a)/b;}, MOD: function(a,b) {b=N(b); return b==0?0:N(a)%b;}, B: function(a) { if (typeof a === 'string') { if (a.trim().length == 0) return 0; return 1; } return a ? 1 : 0; } } };