Mercurial > pub > ImplabJs
changeset 0:fc2517695ee1
Initial commit, draft import of existing work
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.eslintrc.json Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,27 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "amd": true, + "node": true + }, + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "sourceType": "module" + }, + "extends": "eslint:recommended", + "rules": { + "no-const-assign": "warn", + "no-this-before-super": "warn", + "no-undef": "error", + "no-unreachable": "warn", + "no-unused-vars": "warn", + "constructor-super": "warn", + "valid-typeof": "warn", + "semi" : "warn", + "no-invalid-this" : "error", + "no-console": "off" + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/Uri.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,232 @@ +define( + [ "dojo/_base/declare" ], + function(declare) { + function parseURI(uri) { + var schema, host, port, path, query, hash, i; + if (typeof (uri) == "string") { + if ((i = uri.indexOf(":")) >= 0 && + uri.substr(0, i).match(/^\w+$/)) { + schema = uri.substr(0, i); + uri = uri.substr(i + 1); + } + + if (uri.indexOf("//") === 0) { + uri = uri.substr(2); + if ((i = uri.indexOf("/")) >= 0) { + host = uri.substr(0, i); + uri = uri.substr(i); + } else { + host = uri; + uri = ""; + } + } + + if ((i = uri.indexOf("?")) >= 0) { + path = uri.substr(0, i); + uri = uri.substr(i + 1); + + } else { + path = uri; + uri = ""; + + if ((i = path.indexOf("#")) >= 0) { + hash = path.substr(i + 1); + path = path.substr(0, i); + } + } + + if ((i = uri.indexOf("#")) >= 0) { + query = uri.substr(0, i); + hash = uri.substr(i + 1); + } else { + query = uri; + } + } + + if (host && (i = host.lastIndexOf(":")) >= 0) { + port = host.substr(i + 1); + host = host.substr(0, i); + } + + return { + schema : schema, + host : host, + port : port, + path : path, + query : query, + hash : hash + }; + } + + function makeURI(options) { + var uri = []; + + if (options.schema) + uri.push(options.schema, ":"); + if (options.host) + uri.push("//", options.host); + if (options.host && options.port) + uri.push(":", options.port); + + if (options.path) { + if (options.host && options.path[0] != "/") + uri.push("/"); + uri.push(options.path); + } else if (options.host) { + uri.push("/"); + } + + if (options.query) + uri.push("?", options.query); + if (options.hash) + uri.push("#", options.hash); + + return uri.join(""); + } + + function reducePath(parts) { + var balance = 0, result = [], isRoot; + + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + switch (part) { + case "..": + if (balance > 0) { + result.pop(); + } else { + if (isRoot) + throw new Error("Unbalanced path: " + parts); + + result.push(part); + } + balance--; + break; + case ".": + break; + case "": + if (i === 0) { + isRoot = true; + result.push(part); + } + break; + default: + result.push(part); + balance++; + break; + } + } + + return result.join("/"); + } + + var meta = { + schema : null, + host : null, + port : null, + path : null, + query : null, + hash : null + }; + + var URI = declare(null, { + constructor : function(opts) { + if (typeof (opts) == "string") + opts = parseURI(opts); + for ( var p in meta) + if (p in opts) + this[p] = opts[p]; + }, + + clone : function() { + return new URI(this); + }, + + combine : function(rel) { + var me = this; + + if (typeof (rel) === "string") + rel = new URI(rel); + else + rel = rel.clone(); + + // //some.host:123/path?q=a#123 + if (rel.host) + return rel; + + // /abs/path?q=a#123 + if (rel.path && rel.path[0] == "/") { + if (me.host) { + rel.schema = me.schema; + rel.host = me.host; + rel.port = me.port; + } + return rel; + } + + var base = me.clone(); + + // rel/path?a=b#cd + if (rel.path) { + var segments = base.getSegments(); + segments.pop(); + segments.push.apply(segments, rel.getSegments()); + + base.path = reducePath(segments); + } + + // ?q=a#123 + if (rel.query) + base.query = rel.query; + if (rel.hash) + base.hase = rel.hash; + + return base; + }, + + optimize : function() { + this.path = reducePath(this.getSegments()); + }, + + getSegments : function() { + if (typeof (this.path) === "string") + return this.path.split("/"); + else + return []; + }, + + toString : function() { + var uri = [], me = this; + + if (me.schema) + uri.push(me.schema, ":"); + if (me.host) + uri.push("//", me.host); + if (me.host && me.port) + uri.push(":", me.port); + + if (me.path) { + if (me.host && me.path[0] != "/") + uri.push("/"); + uri.push(me.path); + } else if (me.host) { + uri.push("/"); + } + + if (me.query) + uri.push("?", me.query); + if (me.hash) + uri.push("#", me.hash); + + return uri.join(""); + } + + }); + + URI.combine = function(base, rel) { + if (typeof (base) === "string") + base = new URI(base); + return base.combine(rel).toString(); + }; + + return URI; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/Uuid.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,280 @@ +// uuid.js +// +// Copyright (c) 2010-2012 Robert Kieffer +// MIT License - http://opensource.org/licenses/mit-license.php +define( + [], + function () { + 'use strict'; + + var _window = 'undefined' !== typeof window ? window : null; + + // Unique ID creation requires a high quality random # generator. We + // feature + // detect to determine the best RNG source, normalizing to a function + // that + // returns 128-bits of randomness, since that's what's usually required + var _rng, _mathRNG, _nodeRNG, _whatwgRNG, _previousRoot; + + function setupBrowser() { + // Allow for MSIE11 msCrypto + var _crypto = _window.crypto || _window.msCrypto; + + if (!_rng && _crypto && _crypto.getRandomValues) { + // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto + // + // Moderately fast, high quality + try { + var _rnds8 = new Uint8Array(16); + _whatwgRNG = _rng = function whatwgRNG() { + _crypto.getRandomValues(_rnds8); + return _rnds8; + }; + _rng(); + } catch (e) {} + } + + if (!_rng) { + // Math.random()-based (RNG) + // + // If all else fails, use Math.random(). It's fast, but is of + // unspecified + // quality. + var _rnds = new Array(16); + _mathRNG = _rng = function () { + for (var i = 0, r; i < 16; i++) { + if ((i & 0x03) === 0) { + r = Math.random() * 0x100000000; + } + _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff; + } + + return _rnds; + }; + if ('undefined' !== typeof console && console.warn) { + console + .warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()"); + } + } + } + + function setupNode() { + // Node.js crypto-based RNG - + // http://nodejs.org/docs/v0.6.2/api/crypto.html + // + // Moderately fast, high quality + if ('function' === typeof require) { + try { + var _rb = require('crypto').randomBytes; + _nodeRNG = _rng = _rb && function () { + return _rb(16); + }; + _rng(); + } catch (e) {} + } + } + + if (_window) { + setupBrowser(); + } else { + setupNode(); + } + + // Buffer class to use + var BufferClass = ('function' === typeof Buffer) ? Buffer : Array; + + // Maps for number <-> hex string conversion + var _byteToHex = []; + var _hexToByte = {}; + for (var i = 0; i < 256; i++) { + _byteToHex[i] = (i + 0x100).toString(16).substr(1); + _hexToByte[_byteToHex[i]] = i; + } + + // **`parse()` - Parse a UUID into it's component bytes** + function parse(s, buf, offset) { + var i = (buf && offset) || 0, + ii = 0; + + buf = buf || []; + s.toLowerCase().replace(/[0-9a-f]{2}/g, function (oct) { + if (ii < 16) { // Don't overflow! + buf[i + ii++] = _hexToByte[oct]; + } + }); + + // Zero out remaining bytes if string was short + while (ii < 16) { + buf[i + ii++] = 0; + } + + return buf; + } + + // **`unparse()` - Convert UUID byte array (ala parse()) into a string** + function unparse(buf, offset) { + var i = offset || 0, + bth = _byteToHex; + return bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + bth[buf[i++]] + + bth[buf[i++]] + '-' + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]] + bth[buf[i++]]; + } + + // **`v1()` - Generate time-based UUID** + // + // Inspired by https://github.com/LiosK/UUID.js + // and http://docs.python.org/library/uuid.html + + // random #'s we need to init node and clockseq + var _seedBytes = _rng(); + + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = + // 1) + var _nodeId = [ + _seedBytes[0] | 0x01, + _seedBytes[1], + _seedBytes[2], + _seedBytes[3], + _seedBytes[4], + _seedBytes[5] + ]; + + // Per 4.2.2, randomize (14 bit) clockseq + var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff; + + // Previous uuid creation time + var _lastMSecs = 0, + _lastNSecs = 0; + + // See https://github.com/broofa/node-uuid for API details + function v1(options, buf, offset) { + var i = buf && offset || 0; + var b = buf || []; + + options = options || {}; + + var clockseq = (options.clockseq != null) ? options.clockseq : _clockseq; + + // UUID timestamps are 100 nano-second units since the Gregorian + // epoch, + // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so + // time is handled internally as 'msecs' (integer milliseconds) and + // 'nsecs' + // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 + // 00:00. + var msecs = (options.msecs != null) ? options.msecs : new Date() + .getTime(); + + // Per 4.2.1.2, use count of uuid's generated during the current + // clock + // cycle to simulate higher resolution clock + var nsecs = (options.nsecs != null) ? options.nsecs : _lastNSecs + 1; + + // Time since last uuid creation (in msecs) + var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs) / 10000; + + // Per 4.2.1.2, Bump clockseq on clock regression + if (dt < 0 && options.clockseq == null) { + clockseq = clockseq + 1 & 0x3fff; + } + + // Reset nsecs if clock regresses (new clockseq) or we've moved onto + // a new + // time interval + if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) { + nsecs = 0; + } + + // Per 4.2.1.2 Throw error if too many uuids are requested + if (nsecs >= 10000) { + throw new Error( + 'uuid.v1(): Can\'t create more than 10M uuids/sec'); + } + + _lastMSecs = msecs; + _lastNSecs = nsecs; + _clockseq = clockseq; + + // Per 4.1.4 - Convert from unix epoch to Gregorian epoch + msecs += 12219292800000; + + // `time_low` + var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000; + b[i++] = tl >>> 24 & 0xff; + b[i++] = tl >>> 16 & 0xff; + b[i++] = tl >>> 8 & 0xff; + b[i++] = tl & 0xff; + + // `time_mid` + var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff; + b[i++] = tmh >>> 8 & 0xff; + b[i++] = tmh & 0xff; + + // `time_high_and_version` + b[i++] = tmh >>> 24 & 0xf | 0x10; // include version + b[i++] = tmh >>> 16 & 0xff; + + // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant) + b[i++] = clockseq >>> 8 | 0x80; + + // `clock_seq_low` + b[i++] = clockseq & 0xff; + + // `node` + var node = options.node || _nodeId; + for (var n = 0; n < 6; n++) { + b[i + n] = node[n]; + } + + return buf ? buf : unparse(b); + } + + // **`v4()` - Generate random UUID** + + // See https://github.com/broofa/node-uuid for API details + function v4(options, buf, offset) { + // Deprecated - 'format' argument, as supported in v1.2 + var i = buf && offset || 0; + + if (typeof (options) === 'string') { + buf = (options === 'binary') ? new BufferClass(16) : null; + options = null; + } + options = options || {}; + + var rnds = options.random || (options.rng || _rng)(); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + // Copy bytes to buffer, if provided + if (buf) { + for (var ii = 0; ii < 16; ii++) { + buf[i + ii] = rnds[ii]; + } + } + + return buf || unparse(rnds); + } + + // Export public API + var uuid = function() { + return new String(v4()); + }; + uuid.v1 = v1; + uuid.v4 = v4; + uuid.create = v4; + uuid.empty = "00000000-0000-0000-0000-000000000000"; + uuid.parse = parse; + uuid.unparse = unparse; + uuid.BufferClass = BufferClass; + uuid._rng = _rng; + uuid._mathRNG = _mathRNG; + uuid._nodeRNG = _nodeRNG; + uuid._whatwgRNG = _whatwgRNG; + + return uuid; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/components/ActivationController.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,126 @@ +define([ "dojo/_base/declare", "./guard", "./safe", "./_LogMixin" ], + +function(declare, guard, safe, _LogMixin) { + "use strict"; + return declare([ _LogMixin ], { + + _current : null, + + _pending : false, + + getCurrent : function() { + return this._current; + }, + + _start : function() { + if (this._pending) + throw new Error("The activation/decativation is already pending"); + this._pending = true; + }, + + _await : function(d) { + var me = this; + return d.then(function(x) { + me._pending = false; + return x; + }, function(e) { + me._pending = false; + throw e; + }); + }, + + activate : function(component) { + safe.argumentNotNull(component, "component"); + var me = this; + if (component.getController() !== this) + throw new Error("The specified component doesn't belong to this controller"); + + return me._await(guard(me, "_start").then(function() { + me._activate(component); + })); + }, + + _activate : function(component) { + var me = this; + if (me._current === component) + return guard(false); + + // before activation hook + return guard(me, "onActivating", [ component ]).then(function() { + // deactivate curent + if (me._current) + return me._current.deactivate(true).then(function() { + try { + me._current.onDeactivated(); + } catch (err) { + me.error(err); + } + // HACK raise deactivated event + try { + me.onDeactivated(me._current, component); + } catch (err) { + // deactivated shouldn't affect the process + me.error(err); + } + me._current = null; + + }); + }).then(function() { + return component.activate(true); + }).then(function() { + me._current = component; + try { + me.onActivated(component); + } catch (err) { + me.error(err); + } + + }); + + }, + + /** + * Деактивирует текущую компоненту. + * + * @async + * @returns true - компонента была деактивирована, либо нет активной + * компоненты. false - запрос на деактивацию - отклонен. + */ + deactivate : function() { + var me = this; + return me._await(guard(me,"_start").then(function() { + return me._deactivate(); + })); + }, + + _deactivate : function() { + var me = this; + if (!me._current) + return guard(false); + + return guard(me, "onDeactivating").then(function() { + return me._current.deactivate(true); + }).then(function() { + // HACK raise deactivated event + try { + me.onDeactivated(me._current); + } catch (err) { + me.error(err); + } + me._current = null; + }); + }, + + onActivating : function(component) { + }, + + onDeactivating : function(component) { + }, + + onDeactivated : function(component, next) { + }, + + onActivated : function(component) { + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/components/ConsoleLogChannel.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,30 @@ +define( + [ "dojo/_base/declare", "./format" ], + function(declare, format) { + return declare( + null, + { + name : null, + + constructor : function(name) { + this.name = name; + }, + + log : function() { + console.log(this._makeMsg(arguments)); + }, + + warn : function() { + console.warn(this._makeMsg(arguments)); + }, + + error : function() { + console.error(this._makeMsg(arguments)); + }, + + _makeMsg : function(args) { + return this.name ? this.name + " " + + format.apply(null, args) : format.apply(null, args); + } + }); + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/components/StateMachine.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,34 @@ +define([ "dojo/_base/declare", "./safe", "./format" ], function(declare, safe, format) { + return declare(null, { + states : null, + + current : null, + + constructor : function(opts) { + safe.argumentNotNull(opts, "opts"); + safe.argumentNotNull(opts.states, "opts.states"); + safe.argumentNotNull(opts.initial, "opts.initial"); + + this.states = opts.states; + this.current = opts.initial; + + if (safe.isNull(this.states[this.current])) + throw new Error("Invalid initial state " + this.current); + }, + + move : function(input, noThrow) { + safe.argumentNotNull(input, "input"); + + var next = this.states[this.current][input]; + if(safe.isNull(next)) { + if (noThrow) + return false; + else + throw new Error(format("Invalid transition {0}-{1}->?", this.current, input)); + } else { + this.current = next; + return true; + } + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/components/_ActivatableMixin.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,159 @@ +define([ "dojo/_base/declare", "./guard", "./StateMachine", "./_LogMixin", ], + +function(declare, guard, StateMachine, _LogMixin) { + + var states = { + inactive : { + activate : "activating" + }, + activating : { + success : "active", + failed : "inactive" + }, + active : { + deactivate : "deactivating" + }, + deactivating : { + success : "inactive", + failed : "active" + } + }; + + return declare([ _LogMixin ], { + _controller : null, + + _active : null, + + constructor : function() { + this._active = new StateMachine({ + states : states, + initial : "inactive" + }); + }, + + /** + * @returns {Object} контроллер для активации текущей компоненты + */ + getController : function() { + return this._controller; + }, + + /** + * @param {Object} + * v Контроллер для активации текущей компоненты + */ + setController : function(v) { + this._controller = v; + }, + + /** + * @returns {Boolean} текущая компонента активна + */ + isActive : function() { + return this._active.current == "active"; + }, + + assertActive : function() { + if (!this.isActive()) + throw new Error("The object must be active to perform the operation"); + }, + + /** + * Активирует текущую компоненту, если у текущей компоненты задан + * контроллер, то активация будет осуществляться через него + * + * @async + * @param{Boolean} + * direct вызов должен осуществится напрямую, без участия + * контроллера. + * @return{Boolean} успешно/неуспешно + */ + activate : function(direct) { + var me = this; + if (!direct && this._controller) + return me._controller.activate(me).then(function() { + me.onActivated(); + }); + + me._active.move("activate"); + return guard(me, "onActivating").then(function() { + me.log("Activated"); + me._active.move("success"); + if (!me._controller) + me.onActivated(); + }, function(err) { + console.error(err); + me.error("Activation failed: {0}", err); + me._active.move("failed"); + throw err; + }); + }, + + /** + * Деактивирует текущую компоненту, если у компоненты задан контроллер, + * то деактивация будет осуществляться через него. + * + * @async + * @param{Boolean} direct вызов должен осуществится напрямую, без + * участия контроллера. + * + */ + deactivate : function(direct) { + var me = this; + if (!direct && me._controller) + return me._controller.deactivate(me).then(function() { + me.onDeactivated(); + }); + + me._active.move("deactivate"); + return guard(me, "onDeactivating").then(function() { + me.log("Deactivated"); + me._active.move("success"); + if (!me._controller) + me.onDeactivated(); + }, function(err) { + console.error(err); + me.error("Deactivation failed: {0}", err); + me.move("failed"); + throw err; + }); + + }, + + toogleActive : function() { + var me = this; + return (me.isActive() ? me.deactivate() : me.activate()).then(function() { + return me.isActive(); + }); + }, + + /** + * Событие вызывается перед активацией текущей компоненты + * + * @returns{Boolean|undefined} если false - активация будет отменена + */ + onActivating : function() { + }, + + /** + * Событие вызывается перед деактивацией текущей компоненты + * + * @returns {Boolean|undefined} если false - деактивация будет отменена + */ + onDeactivating : function() { + }, + + /** + * Событие вызывается после активации текущей компоненты + */ + onActivated : function() { + }, + + /** + * Событие вызывается после деактивации текущей компоненты + */ + onDeactivated : function() { + } + + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/components/_LogMixin.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,67 @@ +define([ "dojo/_base/declare" ], + +function(declare) { + var cls = declare(null, { + _logChannel : null, + + _logLevel : 1, + + constructor : function(opts) { + if (typeof opts == "object") { + if ("logChannel" in opts) + this._logChannel = opts.logChannel; + if ("logLevel" in opts) + this._logLevel = opts.logLevel; + } + }, + + getLogChannel : function() { + return this._logChannel; + }, + + setLogChannel : function(v) { + this._logChannel = v; + }, + + getLogLevel : function() { + return this._logLevel; + }, + + setLogLevel : function(v) { + this._logLevel = v; + }, + + log : function(format) { + if (this._logChannel && this._logLevel > 2) + this._logChannel.log.apply(this._logChannel, arguments); + }, + warn : function(format) { + if (this._logChannel && this._logLevel > 1) + this._logChannel.warn.apply(this._logChannel, arguments); + }, + error : function(format) { + if (this._logChannel && this._logLevel > 0) + this._logChannel.error.apply(this._logChannel, arguments); + }, + + /** + * Used to by widgets + */ + startup : function() { + var me = this, parent; + if (!me.getLogChannel()) { + parent = me; + while (parent = parent.getParent()) { + if (parent.getLogChannel) { + me.setLogChannel(parent.getLogChannel()); + if(parent.getLogLevel) + me.setLogLevel(parent.getLogLevel()); + break; + } + } + } + this.inherited(arguments); + } + }); + return cls; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/DataContext.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,45 @@ +define([ "dojo/_base/declare", "../safe" ], function(declare, safe) { + return declare( + null, + { + _params : null, + + _repositories : null, + + constructor : function(opts) { + this._params = opts || {}; + this._repositories = {}; + }, + + getRepository : function(name) { + safe.argumentNotEmptyString(name, "name"); + var repo = this._repositories[name]; + if (!repo) { + repo = this._params[name]; + if (!repo) + throw new Error("The repository '" + name + + "' isn't found"); + if (repo instanceof Function) + repo = new repo(); // factory method or constructor + if (repo.initialize) { + repo.initialize({ + dataContext : this + }); + } else if (repo.setDataContext) { + repo.setDataContext(this); + } + this._repositories[name] = repo; + } + + return repo; + }, + + dispose : function() { + for( var name in this._repositories) { + var r = this._repositories[name]; + if (r.dispose) + r.dispose(); + } + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/MapSchema.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,67 @@ +define([ "dojo/_base/declare", "../safe" ], function(declare, safe) { + return declare(null, { + /** + * Отображение одного типа объектов в другой. + * + * @remarks Отображения являются односторонними, т.е. позволяют + * перенести часть содержимого одного объекта в другой. Каждая + * схема отображения строится из набора примитивных + * отображений, которые будут применены в произвольном порядке. + */ + _schema : null, + + constructor : function(schema) { + this._schema = schema; + }, + + /** + * Осуществляет отображение одного объекта в другой + * + * @src{Object} Исходный объект из которого будут взяты данные + * @dst{Object} + */ + map : function(src, dst, ctx) { + safe.argumentNotNull(src, "src"); + safe.argumentNotNull(dst, "dst"); + + for ( var p in this._schema) { + var mapper = this._schema[p]; + if (mapper instanceof Function) { + dst[p] = mapper(src[p]); + } else if (mapper && mapper.map) { + mapper.map(src, dst, p, ctx); + } else { + this._defaultMapper(src, dst, p, mapper, ctx); + } + } + }, + + _defaultMapper : function(src, dst, prop, opts) { + if (typeof (opts) == "string") { + if (opts in src) + dst[prop] = src[opts]; + } else if (opts && opts.type instanceof Function) { + if (src[prop] instanceof opts.type) + dst[prop] = src[prop]; + else + dst[prop] = this._isPrimitiveType(opts.type) ? opts.type + .call(null, src[prop]) : new opts.type(src[prop]); + + } else { + if (!(prop in src)) + if (opts && opts.required) + throw new Error("The " + prop + "is missing"); + else + return; + dst[prop] = src[prop]; + } + }, + + _isPrimitiveType : function(type) { + return (type === String || type === Number || type === Boolean + || type === Number || type === Date); + } + + }); + +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/ObjectStore.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,174 @@ +define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", + "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare, + lang, array, safe, when, Deferred, QueryResults) { + /** + * @module core/data/RestStore + * + * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и + * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования + * данных. + */ + return declare(null, { + + model: null, + + mapping: null, + + _dataContext: null, + + _store : null, // backing store + + _cache : null, + + constructor : function(options) { + options = options || {}; + + if (options.store) + this._store = options.store; + + if (options.dataContext) { + this._dataContext = options.dataContext; + } + + if (options.cache === false) { + // no cache at all + } else if (options.cache === "string" && options.dataContext) { + this._cache = this._dataContext.getCache(options.cache); + } else { + this._cache = {}; + } + }, + + getDataContext : function() { + return this._dataContext; + }, + + // READ + get : function(id) { + var me = this; + var cache = me.getCacheEntry(id); + if (cache) + return cache; + else + return when(me._store.get(id), function(data) { + return me._mapToObject(id, data); + }); + }, + + query : function(query, options) { + var me = this; + var d = me._store.query(query, options); + var result = QueryResults(when(d, function(data) { + return array.map(data, function(item) { + return me._mapToObject(me._store.getIdentity(item), item); + }); + })); + result.total = d.total; + return result; + }, + + getIdentity : function(object) { + return object.getId(); + }, + + // UPDATE + put : function(object, directives) { + return this._store.put(this._mapFromObject(object), directives); + }, + + // INSERT + add : function(object, directives) { + var me = this; + // добавляем в хранилище данные, сохраняем в кеше объект с + // полученным идентификатором + return when( + me._store.add(this._mapFromObject(object), directives), + function(id) { + object.attach(id, me); + me.storeCacheEntry(id, object); + return id; + }); + }, + + // DELETE + remove : function(id) { + var me = this; + return when(me._store.remove(id), function() { + me.removeCacheEntry(id); + }); + }, + + _mapToObject : function(id, data) { + var instance = this.createInstance(id); + this.populateInstance(instance, data); + return instance; + }, + + _mapFromObject : function(object) { + return this.serializeInstance(object); + }, + + getCacheEntry : function(id) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + + return this._cache[id]; + }, + + storeCacheEntry : function(id, object) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + + this._cache[id] = object; + }, + + removeCacheEntry : function(id) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + delete this._cache[id]; + }, + + /** Создает экземпляр сущности с указанным идентификатором, либо извлекает из кеша, если таковая уже имеется. + * @remarks + * Технически сюда можно было бы дополнительно передать данные для ининциализации объекта, + * но концептуально это не верно, поскольку процесс чтения объекта состоит из двух этапов: + * 1. Создание пустого объекта (createInstance) + * 2. Заполнение объекта при помощи схемы отображения (populateInstance) + * при этом первый этап может быть выполнен за долго до второго, например, + * при создании заглушек в процессе установления ссылок между объектами. + */ + createInstance : function(id) { + var instance = this.getCacheEntry(id); + if (!instance) { + instance = this.createInstanceImpl(id); + this.storeCacheEntry(id, instance); + } + return instance; + }, + + /** Непосредственно создает экземпляр сущнсти, т.е. является фабричным методом. + * @param {String} id идентификатор создаваемого экземпляра. + */ + createInstanceImpl : function(id) { + var opts = { + dataContext : this.getDataContext(), + id : id + }; + + return new this.itemsType(opts); + }, + + populateInstance : function(instance, data) { + this.mapping.readData(instance, data,this.getDataContext()); + if (instance.onPopulate) + instance.onPopulate(); + }, + + serializeInstance : function(instance) { + var data = {}; + this.mapping.writeData(instance, data, this.getDataContext()); + return data; + } + + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/RestStore.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,166 @@ +define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", + "../safe", "dojo/when", "dojo/Deferred", "dojo/store/util/QueryResults" ], function(declare, + lang, array, safe, when, Deferred, QueryResults) { + /** + * @module core/data/RestStore + * + * Реализует шаблон репозитария dojo/store над уже имеющимся хранилищем. При получении и + * отправке данных в нижележащие хранилище используется core/data/MapSchema для преобразования + * данных. + */ + return declare(null, { + + itemsType : null, + + _dataContext : null, + + _store : null, // backing store + _cache : null, + + constructor : function(options) { + options = options || {}; + + this._cache = {}; + if (options.store) + this._store = options.store; + if (options.dataContext) + this._dataContext = options.dataContext; + }, + + setDataContext : function(v) { + this._dataContext = v; + }, + + getDataContext : function() { + return this._dataContext; + }, + + // READ + get : function(id) { + var me = this; + var cache = me.getCacheEntry(id); + if (cache) + return cache; + else + return when(me._store.get(id), function(data) { + return me._mapToObject(id, data); + }); + }, + + query : function(query, options) { + var me = this; + var d = me._store.query(query, options); + var result = QueryResults(when(d, function(data) { + return array.map(data, function(item) { + return me._mapToObject(me._store.getIdentity(item), item); + }); + })); + result.total = d.total; + return result; + }, + + getIdentity : function(object) { + return object.getId(); + }, + + // UPDATE + put : function(object, directives) { + return this._store.put(this._mapFromObject(object), directives); + }, + + // INSERT + add : function(object, directives) { + var me = this; + // добавляем в хранилище данные, сохраняем в кеше объект с + // полученным идентификатором + return when( + me._store.add(this._mapFromObject(object), directives), + function(id) { + object.attach(id, me); + me.storeCacheEntry(id, object); + return id; + }); + }, + + // DELETE + remove : function(id) { + var me = this; + return when(me._store.remove(id), function() { + me.removeCacheEntry(id); + }); + }, + + _mapToObject : function(id, data) { + var instance = this.createInstance(id); + this.populateInstance(instance, data); + return instance; + }, + + _mapFromObject : function(object) { + return this.serializeInstance(object); + }, + + getCacheEntry : function(id) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + + return this._cache[id]; + }, + + storeCacheEntry : function(id, object) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + + this._cache[id] = object; + }, + + removeCacheEntry : function(id) { + safe.argumentNotNull(id, "id"); + id = id.toString(); + delete this._cache[id]; + }, + + /** Создает экземпляр сущности с указанным идентификатором, либо извлекает из кеша, если таковая уже имеется. + * @remarks + * Технически сюда можно было бы дополнительно передать данные для ининциализации объекта, + * но концептуально это не верно, поскольку процесс чтения объекта состоит из двух этапов: + * 1. Создание пустого объекта (createInstance) + * 2. Заполнение объекта при помощи схемы отображения (populateInstance) + * при этом первый этап может быть выполнен за долго до второго, например, + * при создании заглушек в процессе установления ссылок между объектами. + */ + createInstance : function(id) { + var instance = this.getCacheEntry(id); + if (!instance) { + instance = this.createInstanceImpl(id); + this.storeCacheEntry(id, instance); + } + return instance; + }, + + /** Непосредственно создает экземпляр сущнсти, т.е. является фабричным методом. + * @param {String} id идентификатор создаваемого экземпляра. + */ + createInstanceImpl : function(id) { + var opts = { + dataContext : this.getDataContext(), + id : id + }; + + return new this.itemsType(opts); + }, + + populateInstance : function(instance, data) { + this.itemsType.readData(instance, data,this.getDataContext()); + if (instance.onPopulate) + instance.onPopulate(); + }, + + serializeInstance : function(instance) { + var data = {}; + this.itemsType.writeData(instance, data, this.getDataContext()); + return data; + } + + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/StatefullStoreAdapter.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,19 @@ +define(["dojo/_base/declare", "dojo/_base/array", "core/safe", "./StoreAdapter"], function(declare, array, safe ,AdapterStore){ + return declare([AdapterStore], { + _attrs : null, + + constructor : function(opts) { + safe.argumentNotEmptyArray(opts.attrs, "opts.attrs"); + this._attrs = opts.attrs; + }, + + mapItem : function(item) { + var result = {}; + array.forEach(this._attrs, function(p) { + result[p] = item.get(p); + }); + return result; + } + }); + +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/StoreAdapter.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,110 @@ +define([ + "dojo/_base/declare", + "core/safe", + "dojo/when", + "dojo/store/util/QueryResults" ], + +function(declare, safe, when, QueryResults) { + + "use strict"; + + /** + * Обертка вокруг произвольного хранилища, только для чтения. Используется + * для преобразования данных, например, отображения в списках элементов + * пространственных данных. + */ + return declare(null, { + /** + * @type{String} Свойство, хранящее идентификатор + */ + idProperty : null, + + _store : null, + + /** + * @param{String} opts.idProperty Имя свойства, в которое будет записан + * идентификатор, если не указан, то идентификатор будет + * взят из родительского хранилища или использоваться + * строка <code>id</code> + * @param{dojo.store} opts.store Родительское хранилище + */ + constructor : function(opts) { + safe.argumentNotNull(opts, "opts"); + safe.argumentNotNull(opts.store, "opts.store"); + + this._store = opts.store; + delete opts.store; + declare.safeMixin(this, opts); + this.idProperty = opts.idProperty || this._store.idProperty || "id"; + }, + + getParentStore : function() { + return this._store; + }, + + get : function(id) { + var me = this; + return when(me._store.get(id), function(x) { + var m = me.mapItem(x); + if (!(me.idProperty in m)) + m[me.idProperty] = id; + return m; + }); + }, + + /** + * Выполняет запрос в родительском хранилище, для этого используется + * <code>translateQuery</code> для подготовки запроса, затем, + * <code>mapItem</code> для преобразования результатов. + */ + query : function(q, options) { + var me = this, store = this._store; + return when(store.query(me.translateQuery(q), me + .translateOptions(options)), function(res) { + var total = res.total; + var mapped = res.map(function(x) { + var m = me.mapItem(x); + if (!(me.idProperty in m)) + m[me.idProperty] = store.getIdentity && + store.getIdentity(x); + return m; + }); + mapped.total = total; + var results = new QueryResults(mapped); + console.log(results); + return results; + }); + }, + + getIdentity : function(obj) { + return obj && obj[this.idProperty]; + }, + + /** + * Преобразование запроса в формат родительского хранилища. + * + * @param{Object} q Запрос в формате текущего хранилища + * @returns{Object} Запрос в формате родительского хранилища + */ + translateQuery : function(q) { + return q; + }, + + translateOptions : function(options) { + return options; + }, + + /** + * Преобразование объекта из родительского хранилища. При преобразовании + * в объекте можно задать идентификатор, иначе идентификатор будет + * автоматически получен и присвоен из родительского хранилища + * + * @param{Object} item Объект из родительского хранилища + * @returns{Object} результат преобразования + */ + mapItem : function(item) { + return item; + } + }); + +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/_ModelBase.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,37 @@ +define(["dojo/_base/declare"], function(declare) { + + return declare(null, { + dataContext : null, + idField : "id", + loaded : false, + + constructor : function(opts){ + if (opts) { + if(opts.dataContext) + this.dataContext = opts.dataContext; + if(opts.id) + this[this.idField] = opts.id; + } + }, + + getId : function() { + return this[this.idField]; + }, + + attach : function(id, dc) { + if (this.dataContext) + throw new Error("The object is already attached"); + this[this.idField] = id; + this.dataContext = dc; + }, + + isAttached : function() { + return this.dataContext ? true : false; + }, + + onPopulate : function() { + this.loaded = true; + } + + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/_StatefulModelMixin.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,5 @@ +define(["dojo/_base/declare", "dojo/Stateful"], function(declare, Stateful) { + return declare([Stateful], { + + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/data/declare-model.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,72 @@ +define([ "dojo/_base/declare", "./_ModelBase", "./MapSchema" ], function( + declare, _ModelBase, MapSchema) { + /** + * Создает новый класс, унаследованный от ./ModelBase, с указанной схемой + * отображения данных. + * + * @details Модель представляет собой объект, живущий в рамках контекста + * данных, также имеющий две схемы отображения: из модели хранения + * в источнике данных (toObjectMap) и наооборот в модель хранения в + * источнике данных (fromObjectMap). + * + * Описание схемы выглядит следующим образом + * <pre> + * { + * name : null, // отображение в обе стороны без преобразования + * + * age : Number, // при преобразоваении к объекту поле будет преобразовано dst.age = Number(src.age) + * // обратное преобразование отсутстсвует + * + * age : [Number, null] // тоже самое что и age : Number + * + * date : [Date, function(v) { return v.toString() }] // указывается преобразование в одну и в другую сторону + * } + * <pre> + */ + return function(schema, mixins, opts) { + var fromObjectSchema = {}, toObjectSchema = {}; + if (schema !== null && schema !== undefined) { + for ( var p in schema) { + var mapper = schema[p]; + + if (mapper instanceof Array) { + toObjectSchema[p] = mapper[0]; + fromObjectSchema[p] = mapper[1]; + } else { + toObjectSchema[p] = mapper; + fromObjectSchema[p] = null; + } + } + } + + if (arguments.length < 3) { + opts = mixins; + mixins = undefined; + } + + var base = [ _ModelBase ]; + if (mixins) { + if (mixins instanceof Array) + base = base.concat(mixins); + else + base.push(mixins); + } + + var model = declare(base, opts); + + model.toObjectMap = new MapSchema(toObjectSchema); + + model.fromObjectMap = new MapSchema(fromObjectSchema); + + model.readData = function(that, data, context) { + model.toObjectMap.map(data, that, context); + }; + + model.writeData = function(that, data, context) { + data = data || {}; + model.fromObjectMap.map(that, data, context); + }; + + return model; + }; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/declare.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,6 @@ +define([ + './declare/_load!' +], function(declare) { + 'use strict'; + return declare; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/declare/_load.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,12 @@ +define([], function () { + 'use strict'; + + return { + load: function (id, require, callback) { + require(['dojo/_base/declare'], function (declare) { + callback(declare); + }); + } + }; + +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/declare/override.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,24 @@ +"use strict"; +define([], function () { + var slice = Array.prototype.slice; + return function (method) { + var proxy; + + /** @this target object */ + proxy = function () { + var me = this; + var inherited = (this.getInherited && this.getInherited(proxy.nom, { + callee: proxy + })) || function () {}; + + method.apply(me, [function () { + return inherited.apply(me, arguments); + }].concat(slice.apply(arguments))); + }; + + proxy.method = method; + proxy.overrides = true; + + return proxy; + }; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ActivationContext.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,139 @@ +define([ + "../declare", + "../safe", + "./Descriptor", + "./ValueDescriptor" + ], + + function (declare, safe, Descriptor, Value) { + var Context = declare(null, { + + _cache: null, + + _services: null, + + _stack: null, + + _visited: null, + + container: null, + + _trace: false, + + constructor: function (container, services, cache, visited) { + safe.argumentNotNull(container, "container"); + safe.argumentNotNull(services, "services"); + + this._visited = visited || {}; + this._stack = []; + this._cache = cache || {}; + this._services = services; + this.container = container; + }, + + getService: function (name, def) { + var d = this._services[name]; + + if (!d) + if (arguments.length > 1) + return def; + else + throw new Error("Service '" + name + "' not found"); + + return d.activate(this, name); + }, + + /** + * registers services local to the the activation context + * + * @name{string} the name of the service + * @service{string} the service descriptor to register + */ + register: function (name, service) { + safe.argumentNotEmptyString(name, "name"); + + if (!(service instanceof Descriptor)) + service = new Value(service, true); + this._services[name] = service; + }, + + clone: function () { + return new Context( + this.container, + safe.create(this._services), + this._cache, + this._visited + ); + + }, + + has: function (id) { + return id in this._cache; + }, + + get: function (id) { + return this._cache[id]; + }, + + store: function (id, value) { + return (this._cache[id] = value); + }, + + parse: function (data, name) { + var me = this; + if (safe.isPrimitive(data)) + return data; + + if (data instanceof Descriptor) { + return data.activate(this, name); + } else if (data instanceof Array) { + me.enter(name); + var v = data.map(function (x, i) { + return me.parse(x, "." + i); + }); + me.leave(); + return v; + } else { + me.enter(name); + var result = {}; + for (var p in data) + result[p] = me.parse(data[p], "." + p); + me.leave(); + return result; + } + }, + + visit: function (id) { + var count = this._visited[id] || 0; + this._visited[id] = count + 1; + return count; + }, + + getStack: function () { + return this._stack.slice().reverse(); + }, + + enter: function (name, d, localize) { + if (this._trace) + console.log("enter " + name + " " + (d || "") + + (localize ? " localize" : "")); + this._stack.push({ + name: name, + service: d, + scope: this._services + }); + if (localize) + this._services = safe.create(this._services); + }, + + leave: function () { + var ctx = this._stack.pop(); + this._services = ctx.scope; + + if (this._trace) + console.log("leave " + ctx.name + " " + (ctx.service || "")); + } + }); + + return Context; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ActivationError.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,39 @@ +define([ + "../declare" +], function (declare) { + return declare(null, { + activationStack: null, + + service: null, + + innerException: null, + + message: null, + + constructor: function (service, activationStack, innerException) { + this.message = "Failed to activate the service"; + this.activationStack = activationStack; + this.service = service; + this.innerException = innerException; + }, + + toString: function () { + var parts = [this.message]; + if (this.service) + parts.push("when activating: " + this.service.toString()); + + if (this.innerException) + parts.push("caused by: " + this.innerException.toString()); + + if (this.activationStack) { + parts.push("at"); + this.activationStack.forEach(function (x) { + parts.push(" " + x.name + " " + + (x.service ? x.service.toString() : "")); + }); + } + + return parts.join("\n"); + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/Container.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,299 @@ +define([ + "../declare", + "../safe", + "../UUID", + "dojo/Deferred", + "./ActivationContext", + "./Descriptor", + "./ValueDescriptor", + "./ReferenceDescriptor", + "./ServiceDescriptor", + "./ActivationError" +], function ( + declare, + array, + safe, + UUID, + Deferred, + ActivationContext, + Descriptor, + Value, + Reference, + Service, + ActivationError) { + var Container = declare(null, { + _services: null, + _cache: null, + _cleanup: null, + _root: null, + _parent: null, + + constructor: function (parent) { + this._parent = parent; + this._services = parent ? safe.create(parent._services) : {}; + this._cache = {}; + this._cleanup = []; + this._root = parent ? parent.getRootContainer() : this; + this._services.container = new Value(this, true); + }, + + getRootContainer: function () { + return this._root; + }, + + getParent: function () { + return this._parent; + }, + + getService: function (name, def) { + var d = this._services[name]; + if (!d) + if (arguments.length > 1) + return def; + else + throw new Error("Service '" + name + "' isn't found"); + if (d.isInstanceCreated()) + return d.getInstance(); + + var context = new ActivationContext(this, this._services); + + try { + return d.activate(context, name); + } catch (error) { + throw new ActivationError(name, context.getStack(), error); + } + }, + + register: function (name, service) { + if (arguments.length == 1) { + var data = name; + for (name in data) + this.register(name, data[name]); + } else { + if (!(service instanceof Descriptor)) + service = new Value(service, true); + this._services[name] = service; + } + return this; + }, + + onDispose: function (callback) { + if (!(callback instanceof Function)) + throw new Error("The callback must be a function"); + this._cleanup.push(callback); + }, + + dispose: function () { + if (this._cleanup) { + for (var i = 0; i < this._cleanup.length; i++) + this._cleanup[i].call(null); + this._cleanup = null; + } + }, + + /** + * @param{String|Object} config + * The configuration of the contaier. Can be either a string or an object, + * if the configuration is an object it's treated as a collection of + * services which will be registed in the contaier. + * + * @param{Function} opts.contextRequire + * The function which will be used to load a configuration or types for services. + * + */ + configure: function (config, opts) { + var p, me = this, + contextRequire = (opts && opts.contextRequire); + + if (typeof (config) === "string") { + p = new Deferred(); + if (!contextRequire) { + var shim = config + "-" + UUID(); + define(shim, ["require", config], function (ctx, data) { + p.resolve([data, { + contextRequire: ctx + }]); + }); + require([shim]); + } else { + // TODO how to get correct contextRequire for the relative config module? + contextRequire([config], function (data) { + p.resolve([data, { + contextRequire: contextRequire + }]); + }); + } + + return p.then(function (args) { + return me._configure.apply(me, args); + }); + } else { + return me._configure(config, opts); + } + }, + + createChildContainer: function () { + return new Container(this); + }, + + has: function (id) { + return id in this._cache; + }, + + get: function (id) { + return this._cache[id]; + }, + + store: function (id, value) { + return (this._cache[id] = value); + }, + + _configure: function (data, opts) { + var typemap = {}, + d = new Deferred(), + me = this, + p, + contextRequire = (opts && opts.contextRequire) || require; + + var services = {}; + + for (p in data) { + var service = me._parse(data[p], typemap); + if (!(service instanceof Descriptor)) + service = new Value(service, false); + services[p] = service; + } + + me.register(services); + + var names = []; + + for (p in typemap) + names.push(p); + + if (names.length) { + contextRequire(names, function () { + for (var i = 0; i < names.length; i++) + typemap[names[i]] = arguments[i]; + d.resolve(me); + }); + } else { + d.resolve(me); + } + return d.promise; + }, + + _parse: function (data, typemap) { + if (safe.isPrimitive(data) || data instanceof Descriptor) + return data; + if (data.$dependency) + return new Reference( + data.$dependency, + data.lazy, + data.optional, + data["default"], + data.services && this._parseObject(data.services, typemap)); + if (data.$value) { + var raw = !data.parse; + return new Value(raw ? data.$value : this._parse( + data.$value, + typemap), raw); + } + if (data.$type || data.$factory) + return this._parseService(data, typemap); + if (data instanceof Array) + return this._parseArray(data, typemap); + + return this._parseObject(data, typemap); + }, + + _parseService: function (data, typemap) { + var me = this, + opts = { + owner: this + }; + if (data.$type) { + + opts.type = data.$type; + + if (typeof (data.$type) === "string") { + typemap[data.$type] = null; + opts.typeMap = typemap; + } + } + + if (data.$factory) + opts.factory = data.$factory; + + if (data.services) + opts.services = me._parseObject(data.services, typemap); + if (data.inject) + opts.inject = data.inject instanceof Array ? array.map( + data.inject, + function (x) { + return me._parseObject(x, typemap); + }) : me._parseObject(data.inject, typemap); + if (data.params) + opts.params = me._parse(data.params, typemap); + + if (data.activation) { + if (typeof (data.activation) === "string") { + switch (data.activation.toLowerCase()) { + case "singleton": + opts.activation = Service.SINGLETON; + break; + case "container": + opts.activation = Service.CONTAINER; + break; + case "hierarchy": + opts.activation = Service.HIERARCHY; + break; + case "context": + opts.activation = Service.CONTEXT; + break; + case "call": + opts.activation = Service.CALL; + break; + default: + throw new Error("Unknown activation type: " + + data.activation); + } + } else { + opts.activation = Number(data.activation); + } + } + + if (data.cleanup) + opts.cleanup = data.cleanup; + + return new Service(opts); + }, + + _parseObject: function (data, typemap) { + if (data.constructor && + data.constructor.prototype !== Object.prototype) + return new Value(data, true); + + var o = {}; + + for (var p in data) + o[p] = this._parse(data[p], typemap); + + return o; + }, + + _parseArray: function (data, typemap) { + if (data.constructor && + data.constructor.prototype !== Array.prototype) + return new Value(data, true); + + var me = this; + return array.map(data, function (x) { + return me._parse(x, typemap); + }); + } + + }); + + return Container; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/Descriptor.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,4 @@ +define([], function() { + // abstract base type for descriptros + return function() {}; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ReferenceDescriptor.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,90 @@ +define([ + "dojo/_base/declare", "../safe", "./Descriptor", "./ActivationError", "./ValueDescriptor" +], + +function(declare, safe, Descriptor, ActivationError, Value) { + return declare(Descriptor, { + _name : null, + _lazy : false, + _optional : false, + _default : undefined, + + constructor : function(name, lazy, optional, def, services) { + safe.argumentNotEmptyString(name, "name"); + this._name = name; + this._lazy = Boolean(lazy); + this._optional = Boolean(optional); + this._default = def; + this._services = services; + }, + + activate : function(context, name) { + var me = this; + + context.enter(name, this, true); + + // добавляем сервисы + if (me._services) { + for ( var p in me._services) { + var sv = me._services[p]; + context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); + } + } + + if (me._lazy) { + // сохраняем контекст активации + context = context.clone(); + return function(cfg) { + // защищаем контекст на случай исключения в процессе + // активации + var ct = context.clone(); + try { + if (cfg) + safe.each(cfg, function(v, k) { + ct.register(k, v instanceof Descriptor ? v : new Value(v, false)); + }); + return me._optional ? ct.getService(me._name, me._default) : ct + .getService(me._name); + } catch (error) { + throw new ActivationError(me._name, ct.getStack(), error); + } + }; + } + + var v = me._optional ? context.getService(me._name, me._default) : context + .getService(me._name); + context.leave(me); + return v; + }, + + isInstanceCreated : function() { + return false; + }, + + toString : function() { + var opts = []; + if (this._optional) + opts.push("optional"); + if (this._lazy) + opts.push("lazy"); + + var parts = [ + "@ref " + ]; + if (opts.length) { + parts.push("{"); + parts.push(opts.join()); + parts.push("} "); + } + + parts.push(this._name); + + if (!safe.isNull(this._default)) { + parts.push(" = "); + parts.push(this._default); + } + + return parts.join(""); + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ServiceDescriptor.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,291 @@ +define( + [ + "dojo/_base/declare", + "../safe", + "dojo/_base/lang", + "dojo/_base/array", + "./Descriptor", + "./ValueDescriptor" + ], + + function (declare, safe, lang, array, Descriptor, Value) { + var SINGLETON_ACTIVATION = 1, + CONTAINER_ACTIVATION = 2, + CONTEXT_ACTIVATION = 3, + CALL_ACTIVATION = 4, + HIERARCHY_ACTIVATION = 5; + + var injectMethod = function (target, method, context, args) { + var m = target[method]; + if (!m) + throw new Error("Method '" + method + "' not found"); + + if (args instanceof Array) + m.apply(target, context.parse(args, "." + method)); + else + m.call(target, context.parse(args, "." + method)); + }; + + var makeClenupCallback = function (target, method) { + if (typeof (method) === "string") { + return function () { + target[method](); + }; + } else { + return function () { + method(target); + }; + } + }; + + var cacheId = 0; + + var cls = declare( + Descriptor, { + _instance: null, + _hasInstance: false, + _activationType: CALL_ACTIVATION, + _services: null, + _type: null, + _typeMap: null, + _factory: null, + _params: undefined, + _inject: null, + _cleanup: null, + _cacheId: null, + _owner: null, + + constructor: function (opts) { + safe.argumentNotNull(opts, "opts"); + safe.argumentNotNull(opts.owner, "opts.owner"); + + this._owner = opts.owner; + + if (!(opts.type || opts.factory)) + throw new Error( + "Either a type or a factory must be specified"); + + if (typeof (opts.type) === "string" && !opts.typeMap) + throw new Error( + "The typeMap is required when the type is specified by its name"); + + if (opts.activation) + this._activationType = opts.activation; + if (opts.type) + this._type = opts.type; + if (opts.params) + this._params = opts.params; + if (opts.inject) + this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject]; + if (opts.services) + this._services = opts.services; + if (opts.factory) + this._factory = opts.factory; + if (opts.typeMap) + this._typeMap = opts.typeMap; + if (opts.cleanup) { + if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) + throw new Error( + "The cleanup parameter must be either a function or a function name"); + + this._cleanup = opts.cleanup; + } + + this._cacheId = ++cacheId; + }, + + activate: function (context, name) { + + // if we have a local service records, register them first + + var instance; + + switch (this._activationType) { + case 1: // SINGLETON + // if the value is cached return it + if (this._hasInstance) + return this._instance; + + var tof = this._type || this._factory; + + // create the persistent cache identifier for the type + if (safe.isPrimitive(tof)) + this._cacheId = this._type; + else + this._cacheId = safe.oid(tof); + + // singletons are bound to the root container + var container = context.container.getRootContainer(); + + if (container.has(this._cacheId)) { + instance = container.get(this._cacheId); + } else { + instance = this._create(context, name); + container.store(this._cacheId, instance); + if (this._cleanup) + container.onDispose( + makeClenupCallback(instance, this._cleanup)); + } + + this._hasInstance = true; + return (this._instance = instance); + + case 2: // CONTAINER + //return a cached value + if (this._hasInstance) + return this._instance; + + // create an instance + instance = this._create(context, name); + + // the instance is bound to the container + if (this._cleanup) + this._owner.onDispose( + makeClenupCallback(instance, this._cleanup)); + + // cache and return the instance + this._hasInstance = true; + return (this._instance = instance); + case 3: // CONTEXT + //return a cached value if one exists + if (context.has(this._cacheId)) + return context.get(this._cacheId); + // context context activated instances are controlled by callers + return context.store(this._cacheId, this._create( + context, + name)); + case 4: // CALL + // per-call created instances are controlled by callers + return this._create(context, name); + case 5: // HIERARCHY + // hierarchy activated instances are behave much like container activated + // except they are created and bound to the child container + + // return a cached value + if (context.container.has(this._cacheId)) + return context.container.get(this._cacheId); + + instance = this._create(context, name); + + if (this._cleanup) + context.container.onDispose(makeClenupCallback( + instance, + this._cleanup)); + + return context.container.store(this._cacheId, instance); + default: + throw "Invalid activation type: " + this._activationType; + } + }, + + isInstanceCreated: function () { + return this._hasInstance; + }, + + getInstance: function () { + return this._instance; + }, + + _create: function (context, name) { + context.enter(name, this, Boolean(this._services)); + + if (this._activationType != CALL_ACTIVATION && + context.visit(this._cacheId) > 0) + throw new Error("Recursion detected"); + + if (this._services) { + for (var p in this._services) { + var sv = this._services[p]; + context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); + } + } + + var instance; + + if (!this._factory) { + var ctor, type = this._type; + + if (typeof (type) === "string") { + ctor = this._typeMap[type]; + if (!ctor) + throw new Error("Failed to resolve the type '" + + type + "'"); + } else { + ctor = type; + } + + if (this._params === undefined) { + this._factory = function () { + return new ctor(); + }; + } else if (this._params instanceof Array) { + this._factory = function () { + var inst = lang.delegate(ctor.prototype); + var ret = ctor.apply(inst, arguments); + return typeof (ret) === "object" ? ret : inst; + }; + } else { + this._factory = function (param) { + return new ctor(param); + }; + } + } + + if (this._params === undefined) { + instance = this._factory(); + } else if (this._params instanceof Array) { + instance = this._factory.apply(this, context.parse( + this._params, + ".params")); + } else { + instance = this._factory(context.parse( + this._params, + ".params")); + } + + if (this._inject) { + array.forEach(this._inject, function (spec) { + for (var m in spec) + injectMethod(instance, m, context, spec[m]); + }); + } + + context.leave(); + + return instance; + }, + + // @constructor {singleton} foo/bar/Baz + // @factory {singleton} + toString: function () { + var parts = []; + + parts.push(this._type ? "@constructor" : "@factory"); + + parts.push(activationNames[this._activationType]); + + if (typeof (this._type) === "string") + parts.push(this._type); + + return parts.join(" "); + } + + }); + + cls.SINGLETON = SINGLETON_ACTIVATION; + cls.CONTAINER = CONTAINER_ACTIVATION; + cls.CONTEXT = CONTEXT_ACTIVATION; + cls.CALL = CALL_ACTIVATION; + cls.HIERARCHY = HIERARCHY_ACTIVATION; + + var activationNames = [ + "", + "{singleton}", + "{container}", + "{context}", + "{call}", + "{hierarchy}" + ]; + + return cls; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ValueDescriptor.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,38 @@ +define([ "dojo/_base/declare", "./Descriptor", "../safe" ], + +function(declare, Descriptor, safe) { + return declare(Descriptor, { + _value : undefined, + _raw : false, + constructor : function(value, raw) { + this._value = value; + this._raw = Boolean(raw); + }, + + activate : function(context, name) { + context.enter(name, this); + var v = this._raw ? this._value : context.parse( + this._value, + ".params"); + context.leave(this); + return v; + }, + + isInstanceCreated : function() { + return this._raw; + }, + + getInstance : function() { + if (!this._raw) + throw new Error("The instance isn't constructed"); + return this._value; + }, + + toString : function() { + if (this._raw) + return "@value {raw}"; + else + return safe.isNull(this._value) ? "@value <null>" : "@value"; + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/guard.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,54 @@ +define([ "dojo/Deferred" ], function(Deferred) { + var toPromise = function(d) { + if (d && d.then) + return d; + var d2 = new Deferred(); + d2.resolve(d); + return d2; + }; + + /** + * функция для асинхронного выполнения метода + * + * @async + * @param{Object} o Объект, который будет использован в качестве + * <code>this</code>, если не указан, будет + * <code>null</code> + * @param{Function|String} m Функция или имя метода, обязательный параметр. + * Если указано имя, тогда параметр <code>o</code> + * также должен быть задан + * @param{Array} args Параметры для вызова метода, не обязательно. + * @returns{dojo/promise} + */ + return function(o, m, args) { + if (arguments.length == 1) { + m = o; + o = null; + } else if (arguments.length == 2 && o instanceof Function && + m instanceof Array) { + args = m; + m = o; + o = null; + } + + try { + if (!(m instanceof Function)) { + if (o) + m = o[m]; + else if (arguments.length == 1) + return toPromise(m); + else + throw new Error("The target object must be specified"); + } + + if (!m) + throw new Error("Method not found"); + + return toPromise(m.apply(o, args)); + } catch (err) { + var d = new Deferred(); + d.reject(err); + return d; + } + }; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/messaging/Client.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,61 @@ +define( + [ "dojo/_base/declare", "dojo/_base/lang", "dojo/Evented", "../_LogMixin" ], + + function(declare, lang, Evented, _LogMixin) { + return declare([ Evented, _LogMixin ], { + _session : null, + _destination : null, + _id : null, + + constructor : function(session, destination, options) { + this._destination = destination; + this._session = session; + }, + + getDestination : function() { + return this._destination; + }, + + start : function() { + var me = this; + return me._session.createClient(me.prepareOptions({})).then( + function(id) { + me._id = id; + return me; + }); + }, + + prepareOptions : function(options) { + var me = this; + options.mode = me.getMode(); + options.destination = me.getDestination(); + options.client = function(msg) { + me.process(msg); + }; + return options; + }, + + process : function(msg) { + this.warn("Messages are not acceped by this client"); + }, + + stop : function() { + var me = this; + if (me._id) { + me.log("stop"); + return me._session.deleteClient({'clientId': me._id}).then(function() { + me._id = null; + return me; + }); + } + }, + + toString : function() { + return "[" + + [ + this.getMode().toUpperCase(), + this.getDestination(), + this._id ].join(',') + "]"; + } + }); + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/messaging/Destination.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,34 @@ +define([ "dojo/_base/declare", "./Listener" ], + +function(declare, Listener) { + return declare(null, { + _session : null, + _destination : null, + _listenerClass : null, + + constructor : function(session, destination, options) { + if (!session) + throw new Error("A session is required"); + if (!destination) + throw new Error("A destination is required"); + + this._session = session; + this._destination = destination; + if (options) { + if (options.listenerClass) + this._listenerClass = options.listenerClass; + } + }, + + listen : function(callback) { + var factory = this._listenerClass || Listener; + var listener = new factory(this._session, this._destination, { + listener : callback + }); + listener.start(); + + return listener; + } + + }); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/messaging/Listener.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,64 @@ +define([ "dojo/_base/declare", "dojo/_base/lang", "./Client" ], + +function(declare, lang, Client) { + return declare([ Client ], { + _listener : null, + + constructor : function(session, destination, options) { + if (!options || !options.listener) + throw new Error("A listener is required"); + this._listener = options.listener; + if (options.transform) + this._transform = options.transform; + }, + + getMode : function() { + return "listener"; + }, + + process : function(result) { + switch (result.type) { + case "message": + try { + this._handleMessage(result.message); + } catch (ex) { + var err = new Error("Failed to handle message"); + err.envelope = result.message; + err.innerException = ex; + this._handleError(err); + } + break; + case "error": + this._handleError(result.error); + break; + } + + }, + + _transform : function(envelope) { + return envelope; + }, + + _handleMessage : function(envelope) { + this.log( + "MESSAGE type = ${0}, headers = ${2}: ${1}", + envelope.bodyType, + envelope.body, + JSON.stringify(envelope.headers)); + var data = this._transform(envelope); + this._listener(data); + this.emit("message", data); + }, + + _handleError : function(ex) { + if (ex.innerException) + this.error( + "ERROR: ${0} -> ${1}", + ex.message, + ex.innerException.message); + else + this.error("ERROR: ${0}", ex.message); + this.emit("error", ex); + } + }); +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/messaging/Session.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,217 @@ +define( + [ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/request", + "./Destination", + "dojo/Evented", + "dojo/Deferred", + "../_LogMixin" ], + + function(declare, lang, request, Destination, Evented, Deferred, _LogMixin) { + + var cls = declare( + [ Evented, _LogMixin ], + { + _id : null, + _baseUrl : null, + _destinations : null, + _timeout : 100000, + _clients : null, + _started : null, + _starting : false, + + constructor : function(baseUrl, options) { + if (!baseUrl) + throw new Error("baseUrl is required"); + options = options || {}; + + this._baseUrl = baseUrl.replace(/\/*$/, ""); + this._destinations = {}; + this._pending = []; + this._clients = {}; + if (options.timeout) + this._timeout = options.timeout; + + this._started = new Deferred(); + }, + + start : function() { + if (this._starting) + return this._started; + this._starting = true; + + var me = this; + me.log("START"); + request(this._baseUrl, { + method : "POST", + handleAs : "json" + }).then(function(result) { + me._id = result; + me._emitConnected(); + me._poll(); + me._started.resolve(me); + }, function(error) { + me._emitError(error); + me._started.reject(me); + }); + return me._started.promise; + }, + + createClient : function(options) { + if (!options || !options.destination || !options.mode) + throw new Error("Invalid argument"); + + var me = this; + + return me._started + .then(function() { + var url = me._makeUrl(me._id); + me.log( + "CREATE mode=${0}, destination=${1}", + options.mode, + options.destination); + + return request(url, { + method : "POST", + data : { + mode : options.mode, + destination : options.destination + }, + handleAs : 'json' + }) + .then( + function(id) { + me + .log( + "CLIENT id=${0}, mode=${1}, destination=${2}", + id, + options.mode, + options.destination); + me._clients[id] = options.client + ? options.client + : function(msg) { + me + .warn( + "The client id=${0}, mode=${1}, destination=${2} isn't accepting mesages", + id, + options.mode, + options.destination); + }; + return id; + }); + }); + + }, + + deleteClient : function(options) { + if (!options || !options.clientId) + throw new Error("Invalid argument"); + + var me = this, id = options.clientId; + + return me._started.then(function() { + var url = me._makeUrl(me._id, options.clientId); + + me.log("DELETE CLIENT ${0}", options.clientId); + + return request(url, { + method : "DELETE", + handleAs : 'json' + }).then(function() { + me.log("CLIENT DELETED ${0}", options.clientId); + me._clients[id] = undefined; + }); + }); + }, + + _poll : function() { + var me = this, url = this._makeUrl(this._id); + me.log("POLL timeout=${0}", me._timeout); + request(url, { + method : "GET", + handleAs : "json", + query : { + timeout : me._timeout + } + }).then(function(response) { + me._handlePoll(response); + me._poll(); + }, function(err) { + me.error("POLL faield with ${0}", err); + me._emitError(err); + }); + }, + + _handlePoll : function(response) { + if (!response) { + this.log("POLL response undefined, looks like a bug"); + return; + } + if (!response.results || !response.results.length) { + this.log("POLL response is empty"); + return; + } + + var results = response.results; + this.log("POLL got ${0} results", results.length); + + for (var i = 0; i < results.length; i++) { + var result = results[i]; + var client = this._clients[result.clientId]; + if (!client) { + // TODO this could happen due to client isn't + // registered yet + this.error("Unknown client ${0}", result.clientId); + continue; + } + client.call(this, result); + } + }, + + _emitError : function(err) { + this.emit("error", err); + }, + + _emitConnected : function() { + var me = this; + me.log("CONNECTED"); + me.emit("connected"); + }, + + _makeUrl : function() { + var parts = [ this._baseUrl ]; + for (var i = 0; i < arguments.length; i++) + parts.push(arguments[i].replace(/\/*$/, "")); + return parts.join('/'); + }, + + queue : function(name) { + return this._getDestination("queue://" + name); + }, + + topic : function(name) { + return this._getDestination("topic://" + name); + }, + + _getDestination : function(uri) { + if (uri in this._destinations) + return this._destinations[uri]; + + var dest = new Destination(this, uri); + this._destinations[uri] = dest; + return dest; + }, + + toString : function() { + return [ "[", "SESSION ", this._id, "]" ].join(" "); + } + }); + + cls.connect = function(url, options) { + var session = new cls(url, options); + return session.start(); + }; + + return cls; + });
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/safe.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,356 @@ +define([], + + function () { + var id = 0, + OID_FIELD = "__core_safe_oid_field", + _create, _defineProperty, _keys; + + if (!Object.keys) { + _defineProperty = function (obj, prop, d) { + if (!d) + throw new Error("Invalid descriptor"); + obj[prop] = d.value; + }; + } else { + _defineProperty = Object.defineProperty; + } + + if (!Object.create) { + var tctor = function () {}; + _create = function (proto) { + if (arguments.length > 1) + throw new Error("The two arguments for isn't supported"); + + tctor.prototype = proto; + return new tctor(); + }; + } else { + _create = Object.create; + } + + if (!Object.keys) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys#Polyfill + _keys = (function () { + 'use strict'; + var hasOwnProperty = Object.prototype.hasOwnProperty, + hasDontEnumBug = !({ + toString: null + }).propertyIsEnumerable('toString'), + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length; + + return function (obj) { + if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { + throw new TypeError('Object.keys called on non-object'); + } + + var result = [], + prop, i; + + for (prop in obj) { + if (prop != OID_FIELD && hasOwnProperty.call(obj, prop)) { + result.push(prop); + } + } + + if (hasDontEnumBug) { + for (i = 0; i < dontEnumsLength; i++) { + if (hasOwnProperty.call(obj, dontEnums[i])) { + result.push(dontEnums[i]); + } + } + } + return result; + }; + }()); + } else { + _keys = Object.keys; + } + + var safe = null; + safe = { + OID_FIELD: OID_FIELD, + + argumentNotNull: function (arg, name) { + if (arg === null || arg === undefined) + throw new Error("The argument " + name + " can't be null or undefined"); + }, + + argumentNotEmptyString: function (arg, name) { + if (typeof (arg) !== "string" || !arg.length) + throw new Error("The argument '" + name + "' must be a not empty string"); + }, + + argumentNotEmptyArray: function (arg, name) { + if (!(arg instanceof Array) || !arg.length) + throw new Error("The argument '" + name + "' must be a not empty array"); + }, + + argumentOfType: function (arg, type, name) { + if (!(arg instanceof type)) + throw new Error("The argument '" + name + "' type doesn't match"); + }, + + isNull: function (arg) { + return (arg === null || arg === undefined); + }, + + isPrimitive: function (arg) { + return (arg === null || arg === undefined || typeof (arg) === "string" || + typeof (arg) === "number" || typeof (arg) === "boolean"); + }, + + isInteger: function (arg) { + return parseInt(arg) === arg; + }, + + isNumber: function (arg) { + return parseFloat(arg) === arg; + }, + + isString: function (val) { + return typeof (val) == "string" || val instanceof String; + }, + + isNullOrEmptyString: function (str) { + if (str === null || str === undefined || + ((typeof (str) == "string" || str instanceof String) && str.length === 0)) + return true; + }, + + isNotEmptyArray: function (arg) { + return (arg instanceof Array && arg.length > 0); + }, + + /** + * Выполняет метод для каждого элемента массива, останавливается, когда + * либо достигнут конец массива, либо функция <c>cb</c> вернула + * значение. + * + * @param{Array | Object} obj массив элементов для просмотра + * @param{Function} cb функция, вызываемая для каждого элемента + * @param{Object} thisArg значение, которое будет передано в качестве + * <c>this</c> в <c>cb</c>. + * @returns Результат вызова функции <c>cb</c>, либо <c>undefined</c> + * если достигнут конец массива. + */ + each: function (obj, cb, thisArg) { + safe.argumentNotNull(cb, "cb"); + var i, x; + if (obj instanceof Array) { + for (i = 0; i < obj.length; i++) { + x = cb.call(thisArg, obj[i], i); + if (x !== undefined) + return x; + } + } else { + var keys = _keys(obj); + for (i = 0; i < keys.length; i++) { + var k = keys[i]; + x = cb.call(thisArg, obj[k], k); + if (x !== undefined) + return x; + } + } + }, + + oid: function (obj) { + return this.isPrimitive(obj) ? undefined : obj[OID_FIELD] || + (_defineProperty(obj, OID_FIELD, { + value: ++id, + enumerable: false + }) && id); + }, + + /** + * Копирует свойства одного объекта в другой. + * + * @param{Any} dest объект в который нужно скопировать значения + * @param{Any} src источник из которого будут копироваться значения + * @tmpl{Object|Array} tmpl шаблон по которому будет происходить + * копирование. Если шаблон является массивом + * (список свойств), тогда значения этого массива + * являются именами свойсвт которые будут + * скопированы. Если шаблон является объектом (карта + * преобразования имен свойств src->dst), тогда + * копирование будет осуществляться только + * собственных свойств источника, присутсвующих в + * шаблоне, при этом значение свойства шаблона + * является именем свойства в которое будет + * произведено коприрование + */ + mixin: function (dest, src, tmpl) { + safe.argumentNotNull(dest, "dest"); + if (!src) + return dest; + + var keys, i, p; + if (arguments.length < 3) { + keys = _keys(src); + for (i = 0; i < keys.length; i++) { + p = keys[i]; + dest[p] = src[p]; + } + } else { + if (tmpl instanceof Array) { + for (i = 0; i < tmpl.length; i++) { + p = tmpl[i]; + if (p in src) + dest[p] = src[p]; + } + + } else { + keys = _keys(src); + for (i = 0; i < keys.length; i++) { + p = keys[i]; + if (p in tmpl) + dest[tmpl[p]] = src[p]; + } + } + } + return dest; + }, + + create: _create, + + delegate: function (target, method) { + if (!(method instanceof Function)) { + this.argumentNotNull(target, "target"); + method = target[method]; + } + + if (!(method instanceof Function)) + throw new Error("'method' argument must be a Function or a method name"); + + return function () { + return method.apply(target, arguments); + }; + }, + + /** + * Для каждого элемента массива вызывает указанную функцию и сохраняет + * возвращенное значение в массиве результатов. + * + * @remarks cb может выполняться асинхронно, при этом одновременно будет + * только одна операция. + * + * @async + */ + pmap: function (items, cb) { + safe.argumentNotNull(cb, "cb"); + + if (items && items.then instanceof Function) + return items.then(function (data) { + return safe.pmap(data, cb); + }); + + if (safe.isNull(items) || !items.length) + return items; + + var i = 0, + result = []; + + function next() { + var r, ri; + + function chain(x) { + result[ri] = x; + return next(); + } + + while (i < items.length) { + r = cb(items[i], i); + ri = i; + i++; + if (r && r.then) { + return r.then(chain); + } else { + result[ri] = r; + } + } + return result; + } + + return next(); + }, + + /** + * Для каждого элемента массива вызывает указанную функцию, результаты + * не сохраняются + * + * @remarks cb может выполняться асинхронно, при этом одновременно будет + * только одна операция. + * @async + */ + pfor: function (items, cb) { + safe.argumentNotNull(cb, "cb"); + + if (items && items.then instanceof Function) + return items.then(function (data) { + return safe.pmap(data, cb); + }); + + if (safe.isNull(items) || !items.length) + return items; + + var i = 0; + + function next() { + while (i < items.length) { + var r = cb(items[i], i); + i++; + if (r && r.then) + return r.then(next); + } + } + + return next(); + }, + + /** + * Выбирает первый элемент из последовательности, или обещания, если в + * качестве параметра используется обещание, оно должно вернуть массив. + * + * @param{Function} cb обработчик результата, ему будет передан первый + * элемент последовательности в случае успеха + * @param{Fucntion} err обработчик исключения, если массив пустой, либо + * не массив + * + * @remarks Если не указаны ни cb ни err, тогда функция вернет либо + * обещание, либо первый элемент. + * @async + */ + first: function (sequence, cb, err) { + if (sequence) { + if (sequence.then instanceof Function) { + return sequence.then(function (res) { + return safe.first(res, cb, err); + }, err); + } else if (sequence && "length" in sequence) { + if (sequence.length === 0) { + if (err) + return err(new Error("The sequence is empty")); + else + throw new Error("The sequence is empty"); + } + return cb ? cb(sequence[0]) : sequence[0]; + } + } + + if (err) + return err(new Error("The sequence is required")); + else + throw new Error("The sequence is required"); + } + }; + + return safe; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/text/format-compile.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,101 @@ +define( + [], + function() { + var map = { + "\\{" : "&curlopen;", + "\\}" : "&curlclose;", + "&" : "&", + "\\:" : ":" + }; + + var rev = { + curlopen : "{", + curlclose : "}", + amp : "&", + colon : ":" + }; + + var espaceString = function(s) { + if (!s) + return s; + return "'" + s.replace(/('|\\)/g, "\\$1") + "'"; + }; + + var encode = function(s) { + if (!s) + return s; + return s.replace(/\\{|\\}|&|\\:/g, function(m) { + return map[m] || m; + }); + }; + + var decode = function(s) { + if (!s) + return s; + return s.replace(/&(\w+);/g, function(m, $1) { + return rev[$1] || m; + }); + }; + + var subst = function(s) { + var i = s.indexOf(":"), name, pattern; + if (i >= 0) { + name = s.substr(0, i); + pattern = s.substr(i + 1); + } else { + name = s; + } + + if (pattern) + return [ + espaceString(decode(name)), + espaceString(decode(pattern)) ]; + else + return [ espaceString(decode(name)) ]; + }; + + var compile = function(str) { + if (!str) + return function() {}; + + var chunks = encode(str).split("{"), chunk; + + var code = [ "var result=[];" ]; + + for (var i = 0; i < chunks.length; i++) { + chunk = chunks[i]; + + if (i === 0) { + if (chunk) + code.push("result.push(" + espaceString(decode(chunk)) + + ");"); + } else { + var len = chunk.indexOf("}"); + if (len < 0) + throw new Error("Unbalanced substitution #" + i); + + code.push("result.push(subst(" + + subst(chunk.substr(0, len)).join(",") + "));"); + if (chunk.length > len + 1) + code.push("result.push(" + + espaceString(decode(chunk.substr(len + 1))) + ");"); + } + } + + code.push("return result.join('');"); + + /* jshint -W054 */ + return new Function("subst", code.join("\n")); + }; + + var cache = {}; + + return function(template) { + var compiled = cache[template]; + if (!compiled) { + compiled = compile(template); + cache[template] = compiled; + } + return compiled; + }; + }); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/text/format.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,85 @@ +define([ + "./safe", + "./format-compile", + "dojo/number", + "dojo/date/locale", + "dojo/_base/array" ], function(safe, compile, number, date, array) { + + // {short,medium,full,long}-{date,time} + var convert = function(value, pattern) { + if (!pattern) + return value.toString(); + + if (pattern.toLocaleLowerCase() == "json") { + var cache = []; + return JSON.stringify(value, function(k, v) { + if (!safe.isPrimitive(v)) { + var id = array.indexOf(cache, v); + if (id >= 0) + return "@ref-" + id; + else + return v; + } else { + return v; + } + }); + } + + if (safe.isNumber(value)) { + var nopt = {}; + if (pattern.indexOf("!") === 0) { + nopt.round = -1; + pattern = pattern.substr(1); + } + nopt.pattern = pattern; + return number.format(value, nopt); + } else if (value instanceof Date) { + var m = pattern.match(/^(\w+)-(\w+)$/); + if (m) + return date.format(value, { + selector : m[2], + formatLength : m[1] + }); + else + return date.format(value, { + selector : "date", + datePattern : pattern + }); + } else { + return value.toString(pattern); + } + }; + + function formatter(format) { + var data; + + if (arguments.length <= 1) + return format; + + data = Array.prototype.slice.call(arguments, 1); + + var template = compile(format); + + return template(function(name, pattern) { + var value = data[name]; + return !safe.isNull(value) ? convert(value, pattern) : ""; + }); + } + + formatter.compile = function(format) { + var template = compile(format); + + return function() { + var data = arguments; + + return template(function(name, pattern) { + var value = data[name]; + return !safe.isNull(value) ? convert(value, pattern) : ""; + }); + }; + }; + + formatter.convert = convert; + + return formatter; +}); \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/text/template-compile.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,54 @@ +define( + [ "dojo/request", "./format" ], + function(request, format) { + var compile = function(str) { + var code = "var p=[],print=function(){p.push(foramt.apply(null,arguments));};" + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + // Convert the template into pure JavaScript + str.replace(/[\r\t\n]/g, " ").split("<%").join("\t").replace( + /(^|%>)[^\t]*/g, + function(x) { + return x.replace(/('|\\)/g, "\\$1"); + }).replace(/\t=(.*?)%>/g, "',$1,'").split("\t").join("');") + .split("%>").join("p.push('") + "');}return p.join('');"; + /* jshint -W054 */ + try { + var compiled = new Function("obj, format, nls", code); + + /** + * Функция форматирования по шаблону + * + * @type{Function} + * @param{Object} obj объект с параметрами для подстановки + */ + return function(obj) { + return compiled(obj || {}, format); + }; + } catch (e) { + if (console && console.error) { + console.error(code); + } + } + }; + + var cache = {}; + + compile.load = function(id, require, callback) { + var url = require.toUrl(id); + if (url in cache) { + callback(cache[url]); + } else { + request(url).then(compile).then(function(tc) { + callback(cache[url] = tc); + }, function(err) { + require.signal("error", [ { + error : err, + src : 'implab/template-compile' + } ]); + }); + } + }; + + return compile; + }); \ No newline at end of file