# HG changeset patch
# User cin
# Date 1496312403 -10800
# Node ID fc2517695ee187317e0bf33ee6f528dd278c269c
Initial commit, draft import of existing work
diff -r 000000000000 -r fc2517695ee1 .eslintrc.json
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/Uri.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/Uuid.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/components/ActivationController.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/components/ConsoleLogChannel.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/components/StateMachine.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/components/_ActivatableMixin.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/components/_LogMixin.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/DataContext.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/MapSchema.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/ObjectStore.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/RestStore.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/StatefullStoreAdapter.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/StoreAdapter.js
--- /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 Имя свойства, в которое будет записан
+ * идентификатор, если не указан, то идентификатор будет
+ * взят из родительского хранилища или использоваться
+ * строка id
+ * @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;
+ });
+ },
+
+ /**
+ * Выполняет запрос в родительском хранилище, для этого используется
+ * translateQuery для подготовки запроса, затем,
+ * mapItem для преобразования результатов.
+ */
+ 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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/_ModelBase.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/_StatefulModelMixin.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/data/declare-model.js
--- /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).
+ *
+ * Описание схемы выглядит следующим образом
+ *
+ * {
+ * name : null, // отображение в обе стороны без преобразования
+ *
+ * age : Number, // при преобразоваении к объекту поле будет преобразовано dst.age = Number(src.age)
+ * // обратное преобразование отсутстсвует
+ *
+ * age : [Number, null] // тоже самое что и age : Number
+ *
+ * date : [Date, function(v) { return v.toString() }] // указывается преобразование в одну и в другую сторону
+ * }
+ *
+ */
+ 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
diff -r 000000000000 -r fc2517695ee1 src/implab/declare.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/declare/_load.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/declare/override.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/ActivationContext.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/ActivationError.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/Container.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/Descriptor.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/ReferenceDescriptor.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/ServiceDescriptor.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/di/ValueDescriptor.js
--- /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 " : "@value";
+ }
+ });
+});
\ No newline at end of file
diff -r 000000000000 -r fc2517695ee1 src/implab/guard.js
--- /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 Объект, который будет использован в качестве
+ * this, если не указан, будет
+ * null
+ * @param{Function|String} m Функция или имя метода, обязательный параметр.
+ * Если указано имя, тогда параметр o
+ * также должен быть задан
+ * @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
diff -r 000000000000 -r fc2517695ee1 src/implab/messaging/Client.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/messaging/Destination.js
--- /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;
+ }
+
+ });
+});
diff -r 000000000000 -r fc2517695ee1 src/implab/messaging/Listener.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/messaging/Session.js
--- /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;
+ });
diff -r 000000000000 -r fc2517695ee1 src/implab/safe.js
--- /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);
+ },
+
+ /**
+ * Выполняет метод для каждого элемента массива, останавливается, когда
+ * либо достигнут конец массива, либо функция cb вернула
+ * значение.
+ *
+ * @param{Array | Object} obj массив элементов для просмотра
+ * @param{Function} cb функция, вызываемая для каждого элемента
+ * @param{Object} thisArg значение, которое будет передано в качестве
+ * this в cb .
+ * @returns Результат вызова функции cb , либо undefined
+ * если достигнут конец массива.
+ */
+ 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
diff -r 000000000000 -r fc2517695ee1 src/implab/text/format-compile.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/text/format.js
--- /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
diff -r 000000000000 -r fc2517695ee1 src/implab/text/template-compile.js
--- /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