UNPKG

toloframework

Version:

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

453 lines (375 loc) 12.7 kB
"use strict"; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } /** @module tfw.binding.property-manager */ require('tfw.binding.property-manager', function (require, module, exports) { var _ = function () { var D = { "en": {}, "fr": {} }, X = require("$").intl; function _() { return X(D, arguments); } _.all = D; return _; }(); "use strict"; /** * @export * @function * @param {object} container - Object which will hold properties. */ module.exports = constructor; /** * @export .isLinkable * Look if an object has a property manager assigned to it and own a * property which name is `propertyName`. */ module.exports.isLinkable = isLinkable; module.exports.getAllAttributesNames = getAllAttributesNames; /** * Return the linkable properties which holds this value, or `null`. */ module.exports.getProperties = getProperties; var Event = require("tfw.event"); var ID = 0; // A container with linkable properties has an attribute with this // name. This attribute own the list of linkable properties. var CONTAINER_SYMBOL = "__tfw.property-manager__"; // A linkable property owned by a container has an attribute with this // name. This attribute give the name of the property and a reference // to the container. var PROPERTY_SYMBOL = '__tfw.binding.property-manager__'; /** * */ function PropertyManager(container) { Object.defineProperty(this, 'id', { value: ID++, writable: false, configurable: false, enumerable: true }); this.name = this.id; this._props = {}; this._container = container; } /** * @class PropertyManager * @member set * Set the inner value of a property without fireing any event. * @param {string} propertyName. * @param {any} value. */ PropertyManager.prototype.set = function (propertyName, value) { this.create(propertyName).set(value); }; /** * Look if `propertyName` is a linkable property of this object. */ PropertyManager.prototype.isLinkable = function (propertyName) { var container = this._container; if (!container) return false; if (typeof container[CONTAINER_SYMBOL] === 'undefined') return false; return typeof container[CONTAINER_SYMBOL]._props[propertyName] !== 'undefined'; }; /** * @class PropertyManager * @member get * @param {string} propertyName. * @return {any} Inner value of the property. * */ PropertyManager.prototype.get = function (propertyName) { return this.create(propertyName).get(); }; PropertyManager.prototype.propertyId = function (propertyName) { return this.create(propertyName).id; }; PropertyManager.prototype.fire = function (propertyName, wave) { var prop = this.create(propertyName); prop.event.fire(prop.get(), propertyName, this._container, wave); }; PropertyManager.prototype.change = function (propertyName, value, wave) { if (typeof wave === 'undefined') wave = []; var prop = this.create(propertyName); var converter = prop.converter; if (typeof converter === 'function') { value = converter(value); } var currentValue = prop.get(); if (prop.alwaysFired || areDifferent(value, currentValue)) { prop.set(value); var that = this; exec(prop, function () { // Fire change event. that.fire(propertyName, wave); }); } }; /** * @class PropertyManager * @member converter * @param {string} propertyName. * @param {function=undefined} converter - Converter is assigned only * if it is a function. * @return {function} Current converter. */ PropertyManager.prototype.converter = function (propertyName, converter) { var prop = this.create(propertyName); if (typeof converter === 'function') { prop.converter = converter; } else if (typeof converter !== 'undefined') { throw Error("[tfw.binding.property-manager::converter] " + "`converter` must be of type function or undefined!"); } return prop.converter; }; PropertyManager.prototype.delay = function (propertyName, delay) { var prop = this.create(propertyName); delay = parseFloat(delay); if (isNaN(delay)) return prop.delay; prop.delay = delay; }; /** * @class PropertyManager * @member on * @param {string} propertyName. * @param {function(val,name,container)} action - Function to execute * when a property changed. */ PropertyManager.prototype.on = function (propertyName, action) { var prop = this.create(propertyName); prop.event.add(action); }; /** * @class PropertyManager * @member off * @param {string} propertyName. * @param {function(val,name,container)} action - Function to execute * when a property changed. */ PropertyManager.prototype.off = function (propertyName, action) { var prop = this.create(propertyName); prop.event.remove(action); }; /** * @export * @function * @param {object} container - Object which will hold properties. */ function constructor(container) { try { if (typeof container === 'undefined') { fail("Argument `container` is mandatory!"); } if (_typeof(container) !== 'object') return null; var pm = container[CONTAINER_SYMBOL]; if (!pm) { pm = new PropertyManager(container); container[CONTAINER_SYMBOL] = pm; } return pm; } catch (ex) { console.error(ex); return null; } } /** * @export .isLinkable * Look if an object has a property manager assigned to it and own a * property which name is `propertyName`. */ function isLinkable(container, propertyName) { if (typeof container[CONTAINER_SYMBOL] === "undefined") return false; if (typeof propertyName !== 'string') return true; return typeof container[CONTAINER_SYMBOL]._props[propertyName] !== "undefined"; } ; function getAllAttributesNames(container) { if (container[CONTAINER_SYMBOL] === undefined) return []; return Object.keys(container[CONTAINER_SYMBOL]._props); } ; /** * Return the linkable properties which holds this value, or `null`. */ function getProperties(property) { var properties = property[PROPERTY_SYMBOL]; if (!Array.isArray(properties)) return null; return properties; } ; /** * @export .create * Create an new linkable property. * @param {string} propertyName - Name of the property. * @param {any} options.init - Initial value. Won't fire any change notification. * @param {function=undefined} options.get - Special getter. * @param {function=undefined} options.set - Special setter. * @param {function=undefined} options.cast - Conversion to apply to * the value before setting it. */ PropertyManager.prototype.create = function (propertyName, options) { var that = this; if (typeof propertyName !== 'string') fail("propertyName must be a string!"); var p = this._props[propertyName]; if (!p) { if (typeof options === 'undefined') options = {}; p = createNewProperty.call(this, propertyName, options); } return p; }; /** * Copy all the attributes of `source` into `target`. */ function applyAttributesToTarget(source, target) { var attName, attValue; for (attName in source) { if (module.exports.isLinkable(target, attName)) { attValue = source[attName]; target[attName] = attValue; } } } function createNewProperty(propertyName, options) { var that = this; var prop = { value: undefined, event: new Event(), filter: functionOrUndefined(options.filter), converter: functionOrUndefined(options.converter), delay: castPositiveInteger(options.delay), action: null, alwaysFired: options.alwaysFired ? true : false, manager: this, name: propertyName, timeout: 0 }; prop.get = createGetter(prop, options); prop.set = createSetter(prop, options, that, propertyName); this._props[propertyName] = prop; if (typeof options.init !== 'undefined') { prop.set(options.init); } Object.defineProperty(this._container, propertyName, { get: prop.get.bind(prop), set: that.change.bind(that, propertyName), enumerable: true, configurable: false }); return prop; } function createGetter(prop, options) { if (typeof options.get === 'function') { return function (v) { var newValue = options.get(v); addPropToValue(prop, newValue); return newValue; }; } return function (v) { return prop.value; }; } function createSetter(prop, options, that, propertyName) { var setter; if (typeof options.cast === 'function') { if (typeof options.set === 'function') { setter = function setter(v) { removePropFromValue(prop, prop.get()); var castedValue = options.cast(v, that); addPropToValue(prop, prop.value); options.set(castedValue); }; } else { setter = function setter(v) { removePropFromValue(prop, prop.get()); prop.value = options.cast(v, that); addPropToValue(prop, prop.value); }; } } else { setter = typeof options.set === 'function' ? options.set : function (v) { removePropFromValue(prop, prop.get()); prop.value = v; addPropToValue(prop, prop.value); }; } return setter; } /** * Add an `info` attribute to the property's value. This is useful to * find the container and the property name from the value. */ function addPropToValue(prop, value) { if (value === undefined || value === null) return; if (value.isContentChangeAware !== true) return; var properties = value[PROPERTY_SYMBOL]; if (!Array.isArray(properties)) { properties = [prop]; } else if (properties.indexOf(prop) === -1) { properties.push(prop); } value[PROPERTY_SYMBOL] = properties; } function removePropFromValue(prop, value) { if (value === undefined || value === null) return; if (value.isContentChangeAware !== true) return; var properties = value[PROPERTY_SYMBOL]; if (!Array.isArray(properties)) return; var pos = properties.indexOf(prop); if (pos === -1) return; properties.splice(pos, 1); } /** * This is a special property which emit a change event as soon as any * value is set to it, even if this value has already been set just * before. Moreover, the value of this attribute is always its name. * This is used for action properties in buttons, for instance. */ PropertyManager.prototype.createAction = function (propertyName, options) { if (typeof options === 'undefined') options = {}; options.alwaysFired = true; return this.create(propertyName, options); }; /** * Most of the time, `action` is called immediatly without any * argument. But you can configure your property with a `delay`. If * you do so, the action is only called after this `delay`(in ms). The * `action`can be lost if another call to `exec()` occurs before the * end of the `delay` and the `delay` is reset. */ function exec(prop, action) { if (!prop.delay) action();else { clearTimeout(prop.timeout); prop.timeout = setTimeout(action, prop.delay); } } ; function fail(msg, source) { if (typeof source === 'undefined') { source = ""; } else { source = "::" + source; } throw Error("[tfw.binding.property-manager" + source + "] " + msg); } function castPositiveInteger(v) { if (typeof v !== 'number') return 0; if (isNaN(v)) return 0; return Math.max(0, Math.floor(v)); } function functionOrUndefined(f) { if (typeof f === 'function') return f; return undefined; } /** * A linkable property must link only if we set a new value to it. If * the value is a List, we need to check if they have a different * inner array because different Lists can share the same array and in * this cas, we don't want to fire a changed event. */ function areDifferent(oldValue, newValue) { return oldValue !== newValue; } module.exports._ = _; });