From 2e04af953463643791f6362bd8ef4c6ba190abfa Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Wed, 18 Apr 2012 13:48:51 -0700 Subject: Squashed commit of the following: commit 2054551bfb01a0f4ca2e138b9d724835462d45cd Merge: 765c2da 616a853 Author: Valerio Virgillito Date: Wed Apr 18 13:48:21 2012 -0700 Merge branch 'refs/heads/master' into integration commit 765c2da8e1aa03550caf42b2bd5f367555ad2843 Author: Valerio Virgillito Date: Tue Apr 17 15:29:41 2012 -0700 updating the picasa carousel Signed-off-by: Valerio Virgillito commit 9484f1c82b81e27edf2dc0a1bcc1fa3b12077406 Merge: d27f2df cacb4a2 Author: Valerio Virgillito Date: Tue Apr 17 15:03:50 2012 -0700 Merge branch 'refs/heads/master' into integration commit d27f2df4d846064444263d7832d213535962abe7 Author: Valerio Virgillito Date: Wed Apr 11 10:39:36 2012 -0700 integrating new picasa carousel component Signed-off-by: Valerio Virgillito commit 6f98384c9ecbc8abe55ccfe1fc25a0c7ce22c493 Author: Valerio Virgillito Date: Tue Apr 10 14:33:00 2012 -0700 fixed the text area case issue Text area was renamed from TextArea to Textarea Signed-off-by: Valerio Virgillito commit 1e83e26652266136802bc7af930379c1ecd631a6 Author: Valerio Virgillito Date: Mon Apr 9 22:10:45 2012 -0700 integrating montage v0.8 into ninja. Signed-off-by: Valerio Virgillito Signed-off-by: Valerio Virgillito --- node_modules/montage/core/deserializer.js | 352 +++- .../montage/core/event/action-event-listener.js | 4 +- node_modules/montage/core/event/binding.js | 48 +- node_modules/montage/core/event/event-manager.js | 14 +- node_modules/montage/core/logger.js | 1 + node_modules/montage/core/promise-connection.js | 285 ++++ node_modules/montage/core/promise-queue.js | 65 + node_modules/montage/core/promise.js | 48 +- node_modules/montage/core/serializer.js | 328 +++- node_modules/montage/core/undo-manager.js | 12 +- .../lab/sandbox/ui/picasa-carousel-test/index.html | 77 - .../ui/picasa-carousel-test/main.reel/main.css | 7 - .../ui/picasa-carousel-test/main.reel/main.html | 75 - .../ui/picasa-carousel-test/main.reel/main.js | 35 - .../sandbox/ui/picasa-carousel-test/package.json | 10 - node_modules/montage/montage.js | 45 +- node_modules/montage/package.json | 2 +- node_modules/montage/require/require.js | 213 ++- .../montage/test/ui/youtube-player-spec.js | 363 ----- .../youtube-player-test/youtube-player-test.html | 47 - .../ui/youtube-player-test/youtube-player-test.js | 9 - .../montage/ui/bluemoon/slider.reel/slider.js | 14 +- node_modules/montage/ui/button.reel/button.js | 95 +- node_modules/montage/ui/check-input.js | 4 +- node_modules/montage/ui/component.js | 23 +- .../montage/ui/composer/long-press-composer.js | 232 --- node_modules/montage/ui/composer/press-composer.js | 126 +- .../montage/ui/composer/translate-composer.js | 133 +- .../montage/ui/condition.reel/condition.js | 2 +- node_modules/montage/ui/flow-bezier-spline.js | 405 +++++ node_modules/montage/ui/flow-path-cubic.js | 115 -- node_modules/montage/ui/flow-path-lerp.js | 112 -- node_modules/montage/ui/flow-path-linear.js | 83 - node_modules/montage/ui/flow-path-sigmoid.js | 148 -- node_modules/montage/ui/flow-path.js | 215 --- node_modules/montage/ui/flow.reel/flow.html | 48 +- node_modules/montage/ui/flow.reel/flow.js | 1002 +++++++++--- node_modules/montage/ui/list.reel/list.html | 38 +- .../ui/loading-panel.reel/loading-panel.html | 32 +- node_modules/montage/ui/native-control.js | 14 +- .../ui/picasa-carousel.reel/image.reel/image.html | 3 +- .../ui/picasa-carousel.reel/picasa-carousel.css | 15 +- .../ui/picasa-carousel.reel/picasa-carousel.html | 242 +-- .../ui/picasa-carousel.reel/picasa-carousel.js | 210 ++- .../montage/ui/popup/alert.reel/alert.html | 32 +- .../montage/ui/popup/confirm.reel/confirm.html | 32 +- .../montage/ui/popup/notifier.reel/notifier.js | 4 +- node_modules/montage/ui/popup/popup.reel/popup.js | 135 +- .../montage/ui/range-input.reel/range-input.js | 6 +- .../montage/ui/repetition.reel/repetition.js | 18 +- .../rich-text-linkpopup.css | 31 + .../rich-text-linkpopup.html | 37 + .../rich-text-linkpopup.js | 274 ++++ .../rich-text-resizer.reel/rich-text-resizer.css | 141 ++ .../rich-text-resizer.reel/rich-text-resizer.html | 45 + .../rich-text-resizer.reel/rich-text-resizer.js | 568 +++++++ .../rich-text-editor.reel/rich-text-editor-base.js | 1706 ++++++++++++++++++++ .../rich-text-editor.reel/rich-text-editor.css | 30 + .../rich-text-editor.reel/rich-text-editor.html | 41 + .../rich-text-editor.reel/rich-text-editor.js | 614 +++++++ .../rich-text-editor.reel/rich-text-sanitizer.js | 132 ++ .../montage/ui/scroller.reel/scroller.html | 56 +- node_modules/montage/ui/scroller.reel/scroller.js | 49 +- .../montage/ui/select-input.reel/select-input.js | 7 +- .../ui/skeleton/range-input.reel/range-input.html | 19 +- node_modules/montage/ui/tabs.reel/tabs.html | 20 +- node_modules/montage/ui/template.js | 313 +++- node_modules/montage/ui/text-input.js | 24 +- node_modules/montage/ui/textarea.reel/textarea.js | 4 +- .../montage/ui/video-player.reel/video-player.html | 24 +- 70 files changed, 6978 insertions(+), 2735 deletions(-) create mode 100644 node_modules/montage/core/promise-connection.js create mode 100644 node_modules/montage/core/promise-queue.js delete mode 100755 node_modules/montage/lab/sandbox/ui/picasa-carousel-test/index.html delete mode 100755 node_modules/montage/lab/sandbox/ui/picasa-carousel-test/main.reel/main.css delete mode 100755 node_modules/montage/lab/sandbox/ui/picasa-carousel-test/main.reel/main.html delete mode 100755 node_modules/montage/lab/sandbox/ui/picasa-carousel-test/main.reel/main.js delete mode 100755 node_modules/montage/lab/sandbox/ui/picasa-carousel-test/package.json delete mode 100755 node_modules/montage/test/ui/youtube-player-spec.js delete mode 100644 node_modules/montage/test/ui/youtube-player-test/youtube-player-test.html delete mode 100755 node_modules/montage/test/ui/youtube-player-test/youtube-player-test.js delete mode 100644 node_modules/montage/ui/composer/long-press-composer.js create mode 100755 node_modules/montage/ui/flow-bezier-spline.js delete mode 100644 node_modules/montage/ui/flow-path-cubic.js delete mode 100644 node_modules/montage/ui/flow-path-lerp.js delete mode 100644 node_modules/montage/ui/flow-path-linear.js delete mode 100644 node_modules/montage/ui/flow-path-sigmoid.js delete mode 100644 node_modules/montage/ui/flow-path.js create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.css create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.html create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-linkpopup.reel/rich-text-linkpopup.js create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.css create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.html create mode 100644 node_modules/montage/ui/rich-text-editor/overlays/rich-text-resizer.reel/rich-text-resizer.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor-base.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.css create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.html create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-editor.js create mode 100644 node_modules/montage/ui/rich-text-editor/rich-text-editor.reel/rich-text-sanitizer.js (limited to 'node_modules/montage') diff --git a/node_modules/montage/core/deserializer.js b/node_modules/montage/core/deserializer.js index 7e812235..b8ed5618 100755 --- a/node_modules/montage/core/deserializer.js +++ b/node_modules/montage/core/deserializer.js @@ -97,6 +97,26 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri this._origin = null; }}, + /** + Initializes the deserializer with a string + @param {String|Object} serialization A string or JSON-style object + describing the serialized objects. + @param {Function} require The module loader for the containing package. + @param {String} origin Usually a file name. + */ + init: { + value: function (serialization, require, origin) { + if (typeof serialization !== "string") { + serialization = JSON.stringify(serialization); + } + this._reset(); + this._serializationString = serialization; + this._require = require; + this._origin = origin; + return this; + } + }, + /** Initializes the deserializer with a string of serialized objects. @function @@ -122,6 +142,14 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri return this; }}, + initWithObjectAndRequire: {value: function(string, require, origin) { + this._reset(); + this._serializationString = JSON.stringify(object); + this._require = require; + this._origin = origin; + return this; + }}, + /** Initializes the deserializer object with a serialization string and the require object used to load the modules containing the object's prototypes. @function @@ -168,8 +196,37 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri return objectsArray; }}, + + chainDeserializer: { + value: function(deserializer) { + var chainedSerializations = this._chainedSerializations, + optimizedIds, chainedOptimizedIds; + + if (!chainedSerializations) { + this._chainedSerializations = chainedSerializations = []; + } + + chainedSerializations.push({ + string: deserializer._serializationString, + compiledFunction: deserializer._compiledDeserializationFunction, + compiledFunctionString: deserializer._compiledDeserializationFunctionString + }); + + // need to copy the optimized ids too, ideally all chained templates are optimized for the same document + chainedOptimizedIds = deserializer._optimizedIds; + if (chainedOptimizedIds) { + if (!optimizedIds) { + this._optimizedIds = optimizedIds = {}; + } + for (var id in chainedOptimizedIds) { + optimizedIds[id] = chainedOptimizedIds[id]; + } + } + } + }, + /** - This function is to be used in the context of deserializeSelf delegate used for custom object deserializations. + This function is to be used in the context of deserializeProperties delegate used for custom object deserializations. It reads an entry from the "properties" serialization unit of the object being deserialized. @function @param {string} name The name of the entry to be read. @@ -182,8 +239,93 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri return stack[ix][name]; }}, + deserializeProperties: { + value: function() { + var stack = this._objectStack, + ix = stack.length - 1, + object = stack[ix-1], + desc = stack[ix]; + + this._deserializeProperties(object, desc.properties); + } + }, + + getProperty: { + value: function(name) { + var stack = this._objectStack, + ix = stack.length - 1, + desc = stack[ix]; + + return desc.properties[name]; + } + }, + + deserializeUnits: { + value: function() { + var stack = this._objectStack, + ix = stack.length - 1, + desc = stack[ix]; + + desc._units = this._indexedDeserializationUnits; + } + }, + + deserializeUnit: { + value: function(name) { + var stack = this._objectStack, + ix = stack.length - 1, + desc = stack[ix], + units; + + if (desc._units) { + units = desc._units; + } else { + desc._units = units = {}; + } + + units[name] = this._indexedDeserializationUnits[name]; + } + }, + + getType: { + value: function() { + var stack = this._objectStack, + ix = stack.length - 1, + desc = stack[ix]; + + return "object" in desc ? "object" : ("prototype" in desc ? "prototype" : null); + } + }, + + getTypeValue: { + value: function() { + var stack = this._objectStack, + ix = stack.length - 1, + desc = stack[ix]; + + return desc.object || desc.prototype; + } + }, + + getObjectByLabel: { + value: function(label) { + return this._objects[label] || this._objectLabels[label]; + } + }, + + _customDeserialization: { + enumerable: false, + value: function(object, desc) { + this._pushContextObject(object); + this._pushContextObject(desc); + object.deserializeSelf(this); + this._popContextObject(); + this._popContextObject(); + } + }, + /** - This function is to be used in the context of deserializeSelf delegate used for custom object deserializations. + This function is to be used in the context of deserializeProperties delegate used for custom object deserializations. It deserializes all the named properties of a serialized object into the object given. @function @param {Object} object The target of the properties. @@ -383,7 +525,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri var serialization = this._serialization, moduleIds = this._requiredModuleIds = [], modules = this._modules, - desc, moduleId; + desc, moduleId, name, objectLocation; for (var label in serialization) { desc = serialization[label]; @@ -391,7 +533,8 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri if ("module" in desc) { moduleId = desc.module; - } else if (name = /*assignment*/(desc.prototype || desc.object)) { + } else if ("prototype" in desc || "object" in desc) { + name = desc.prototype || desc.object objectLocation = name.split("["); moduleId = objectLocation[0]; desc.module = moduleId; @@ -437,24 +580,30 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri } }, + _labelRegexp: { + enumerable: false, + value: /^[a-zA-Z_$][0-9a-zA-Z_$]*$/ + }, + /** @private */ - _compileAndDeserialize: {value: function(element, deserialize) { + _compileAndDeserialize: {value: function(element, serialization, exports, deserialize) { var self = this, - serialization, exportsStrings = "", unitsStrings = "", objectsStrings = "", cleanupStrings = "", valueString, - exports = {}, + deserialized = {}, modules = this._modules, idsToRemove = [], optimizedIds = this._optimizedIds, + compiledDeserializationFunctionString, requireStrings = [], objectNamesCounter = {}, - label; + label, + labelRegexp = this._labelRegexp; if (canEval) { serialization = this._serialization; @@ -463,9 +612,13 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri } for (label in serialization) { + if (!labelRegexp.test(label)) { + logger.error("Invalid label format '" + label + "' " + (this._origin ? " in " + this._origin : "")); + throw "Invalid label format: " + label; + } var objectDesc = serialization[label]; - if (label in exports) { + if (label in deserialized) { // already deserialized, in a reference most likely continue; } @@ -475,6 +628,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri exportsStrings += 'var ' + label + ' = exports.' + label + ' = ' + valueString + ';\n'; if (deserialize) { exports[label] = objectDesc.value; + deserialized[label] = true; } // kind of lame but it's just to prevent the need to check whether it's a value or an object in the next serialization loop to deserialize the units. delete serialization[label]; @@ -487,43 +641,43 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri for (label in serialization) { self._deserializeUnits(exports[label], serialization[label]); } + for (label in serialization) { + delete exports[label].isDeserializing; + } } if (idsToRemove.length > 0) { - cleanupStrings = 'element.getElementById("' + idsToRemove.join('").removeAttribute("id");\nelement.getElementById("') + '").removeAttribute("id");'; + cleanupStrings += 'element.getElementById("' + idsToRemove.join('").removeAttribute("id");\nelement.getElementById("') + '").removeAttribute("id");'; for (var i = 0, id; (id = idsToRemove[i]); i++) { element.getElementById(idsToRemove[i]).removeAttribute("id"); } } if (canEval) { - this._compiledDeserializationFunctionString = "(function() {\n" + requireStrings.join("\n") + "\nreturn function(element) {\nvar exports = {};\n" + exportsStrings + "\n\n" + objectsStrings + "\n\n" + unitsStrings + "\n\n" + cleanupStrings + "\nreturn exports;\n}}).call(this)"; - this._serializationString = this._serialization = serialization = null; + compiledDeserializationFunctionString = "(function() {\n" + requireStrings.join("\n") + "\nreturn function(element, exports) {\n" + exportsStrings + "\n\n" + objectsStrings + "\n\n" + unitsStrings + "\n\n" + cleanupStrings + "\nreturn exports;\n}}).call(this)"; } - if (logger.isDebug) { - logger.debug(this._compiledDeserializationFunctionString); + logger.debug(compiledDeserializationFunctionString); } - return exports; + return compiledDeserializationFunctionString; function deserializeObject(label, desc) { var moduleId, name, - instance, objectName, fqn, - properties = desc.properties, isType, - object, + object = self._objectLabels[label], + hasObject = object != null, counter, - propertiesString, + descString, objectLocation; if ("module" in desc) { moduleId = desc.module; objectName = name = desc.name; - } else { + } else if ("prototype" in desc || "object" in desc) { objectLocation = (desc.prototype || desc.object).split("["); // this code is actually only used when canEval == false, // module+name are added when the modules are parsed but it's @@ -542,8 +696,8 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri fqn = moduleId + "." + name; if (deserialize) { - if (self._objectLabels[label]) { - exports[label] = object = self._objectLabels[label]; + if (hasObject) { + exports[label] = object; } else if (isType) { exports[label] = object = modules[moduleId][name]; } else { @@ -558,50 +712,64 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri value: self.uuid + "-" + label }); } - } else { - // need to know if it has been already compiled - exports[label] = true; } + deserialized[label] = true; - if (fqn in requireStrings) { - objectName = requireStrings[fqn]; - } else { - counter = (objectNamesCounter[name] || 0) + 1; - objectNamesCounter[name] = counter; - if (counter > 1) { - objectName += counter; + exportsStrings += 'if (this._objectLabels["' + label + '"]) {\n'; + exportsStrings += ' var ' + label + ' = exports.' + label + ' = this._objectLabels["' + label + '"];\n'; + exportsStrings += '} else if(exports.' + label +') {\n'; + exportsStrings += ' var ' + label + ' = exports.' + label + ';\n'; + if (!hasObject) { + // this block of code is only needed for when there's a + // prototype/object in the serialization, which is the common + // case, but we also support missing object creation + // information as long as the user defines an object to be used + if (fqn in requireStrings) { + objectName = requireStrings[fqn]; + } else { + counter = (objectNamesCounter[name] || 0) + 1; + objectNamesCounter[name] = counter; + if (counter > 1) { + objectName += counter; + } + requireStrings[fqn] = objectName; + requireStrings.push('var ' + objectName + ' = this._modules["' + moduleId + '"]["' + name + '"];'); } - requireStrings[fqn] = objectName; - requireStrings.push('var ' + objectName + ' = this._modules["' + moduleId + '"]["' + name + '"];'); - } - exportsStrings += 'if (this._objectLabels["' + label + '"]) {\n'; - exportsStrings += ' var ' + label + ' = exports.' + label + ' = this._objectLabels["' + label + '"]\n'; - exportsStrings += '} else {\n'; - if (isType) { - exportsStrings += ' var ' + label + ' = exports.' + label + ' = ' + objectName + ';\n'; - } else { - exportsStrings += ' var ' + label + ' = exports.' + label + ' = ' + objectName + '.create();\n'; - exportsStrings += ' Montage.getInfoForObject(' + label + ').label = "' + label + '";\n'; - exportsStrings += ' Object.defineProperty(' + label + ', "_suuid", {enumerable: false, value: "' + self.uuid + '-' + label + '"});\n'; + exportsStrings += '} else {\n'; + if (isType) { + exportsStrings += ' var ' + label + ' = exports.' + label + ' = ' + objectName + ';\n'; + } else { + exportsStrings += ' var ' + label + ' = exports.' + label + ' = ' + objectName + '.create();\n'; + exportsStrings += ' Montage.getInfoForObject(' + label + ').label = "' + label + '";\n'; + exportsStrings += ' Object.defineProperty(' + label + ', "_suuid", {enumerable: false, value: "' + self.uuid + '-' + label + '"});\n'; + } } exportsStrings += '}\n'; - propertiesString = deserializeValue(properties); - objectsStrings += 'this._deserializeProperties(' + label + ', ' + propertiesString + ');\n'; - if (deserialize) { - self._deserializeProperties(object, properties); - } + descString = deserializeValue(desc); - delete desc.module; - delete desc.name; - delete desc.object; - delete desc.properties; + objectsStrings += 'var ' + label + 'Serialization = ' + descString + ';\n'; + objectsStrings += label + '.isDeserializing = true;\n'; + cleanupStrings += 'delete ' + label + '.isDeserializing;\n'; + objectsStrings += 'if (typeof ' + label + '.deserializeSelf === "function") {\n'; + objectsStrings += ' ' + label + 'Serialization._units = {};\n'; + objectsStrings += ' this._customDeserialization(' + label + ', ' + descString + ');\n'; + objectsStrings += '} else {\n'; + objectsStrings += ' this._deserializeProperties(' + label + ', ' + label + 'Serialization.properties);\n'; + objectsStrings += '}\n'; - propertiesString = deserializeValue(desc); - if (propertiesString !== "{}") { - unitsStrings += 'this._deserializeUnits(' + label + ', ' + propertiesString + ');\n'; + if (deserialize) { + object.isDeserializing = true; + if (typeof object.deserializeSelf === "function") { + desc._units = {}; + self._customDeserialization(object, desc); + } else { + self._deserializeProperties(object, desc.properties); + } } + + unitsStrings += 'this._deserializeUnits(' + label + ', ' + label + 'Serialization);\n'; } function deserializeValue(value, parent, key) { @@ -621,7 +789,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri } else if ("@" in value) { type = "reference"; value = value["@"]; - } else if ("->" in value) { + } else if (typeof value["->"] === "object") { type = "function"; value = value["->"]; } else if ("." in value && Object.keys(value).length === 1) { @@ -719,6 +887,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri } } }}, + /** * @private */ @@ -748,26 +917,44 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri */ _deserialize: { value: function(sourceDocument, targetDocument) { - var exports; + var exports = this._objects = {}, + chainedSerializations = this._chainedSerializations; // third and next runs, execute the compiled deserialization function if (this._compiledDeserializationFunction) { - exports = this._compiledDeserializationFunction(sourceDocument); + this._compiledDeserializationFunction(sourceDocument, exports); // second run, create the function and execute it } else if (this._compiledDeserializationFunctionString) { this._compiledDeserializationFunction = eval(this._compiledDeserializationFunctionString); - exports = this._compiledDeserializationFunction(sourceDocument); + this._compiledDeserializationFunction(sourceDocument, exports); // first run, deserialize and create the source of the compiled deserialization function } else { - exports = this._compileAndDeserialize(sourceDocument, true); + this._compiledDeserializationFunctionString = this._compileAndDeserialize(sourceDocument, this._serialization, exports, true); + this._serialization = null; + } + + if (chainedSerializations) { + for (var i = 0, serialization; (serialization = chainedSerializations[i]); i++) { + if (serialization.compiledFunction) { + serialization.compiledFunction.call(this, sourceDocument, exports); + // second run, create the function and execute it + } else if (serialization.compiledFunctionString) { + serialization.compiledFunction = eval(serialization.compiledFunctionString); + serialization.compiledFunction.call(this, sourceDocument, exports); + // first run, deserialize and create the source of the compiled deserialization function + } else { + serialization.compiledFunctionString = this._compileAndDeserialize(sourceDocument, serialization.object, exports, true); + serialization.object = null; + } + } } if (targetDocument) { targetDocument.adoptNode(sourceDocument.body.firstChild); } - return (this._objects = exports); + return exports; } }, @@ -792,8 +979,8 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri */ deserializeObjectWithElement: { value: function(element, callback) { - return this.deserializeWithInstancesAndElementForDocument(null, element, null, function(exports) { - callback(exports ? exports.root : undefined); + return this.deserializeWithInstancesAndElementForDocument(null, element, null, function(exports, element) { + callback(exports ? exports.root : undefined, element); }); } }, @@ -853,7 +1040,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri self._invokeDeserializedFromSerialization(exports); - callback(exports); + callback(exports, body); }); } }, @@ -877,7 +1064,7 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri var exports = self._deserialize(sourceDocument); self._invokeDeserializedFromSerialization(exports); - callback(exports); + callback(exports, sourceDocument); }); } }, @@ -905,32 +1092,47 @@ var Deserializer = Montage.create(Montage, /** @lends module:montage/core/deseri @private */ _deserializeProperties: {value: function(object, properties) { - object.isDeserializing = true; - if (object.deserializeSelf) { + if (object.deserializeProperties) { this._pushContextObject(properties); - object.deserializeSelf(this); + object.deserializeProperties(this); this._popContextObject(); } else { this.deserializePropertiesForObject(object, properties); } - delete object.isDeserializing; }}, /** @private */ _deserializeUnits: {value: function(object, serializedUnits) { - var units = this._indexedDeserializationUnits; + var units = serializedUnits._units || this._indexedDeserializationUnits; - for (var unit in serializedUnits) { - var unitFunction = units[unit]; - if (unitFunction) { - unitFunction(object, serializedUnits[unit]); + for (var unit in units) { + if (unit in serializedUnits) { + units[unit](object, serializedUnits[unit], this); } } }} }); +/** +@function +@param {String} serialization +@param require +@returns promise for the serialized object +*/ +var deserializer = Deserializer.create(); +function deserialize(serialization, require, origin) { + var deferred = Promise.defer(); + deserializer.init(serialization, require, origin) + .deserializeObject(function (object) { + deferred.resolve(object); + }); + return deferred.promise; +} + if (typeof exports !== "undefined") { exports.Deserializer = Deserializer; + exports.deserialize = deserialize; } + diff --git a/node_modules/montage/core/event/action-event-listener.js b/node_modules/montage/core/event/action-event-listener.js index bbe5baad..4fc75f64 100755 --- a/node_modules/montage/core/event/action-event-listener.js +++ b/node_modules/montage/core/event/action-event-listener.js @@ -66,9 +66,9 @@ var ActionEventListener = exports.ActionEventListener = Montage.create(Montage, /** @private */ - serializeSelf: { + serializeProperties: { value: function(serializer) { - serializer.setReference("handler", this.handler); + serializer.set("handler", this.handler, "reference"); serializer.set("action", this.action); } } diff --git a/node_modules/montage/core/event/binding.js b/node_modules/montage/core/event/binding.js index 21b40609..709a5d85 100755 --- a/node_modules/montage/core/event/binding.js +++ b/node_modules/montage/core/event/binding.js @@ -18,6 +18,7 @@ var Montage = require("montage").Montage, ChangeTypes = require("core/event/mutable-event").ChangeTypes, Serializer = require("core/serializer").Serializer, Deserializer = require("core/deserializer").Deserializer, + logger = require("core/logger").logger("binding"); defaultEventManager = require("core/event/event-manager").defaultEventManager, AT_TARGET = 2, UNDERSCORE = "_"; @@ -1184,11 +1185,14 @@ var BindingDescriptor = exports.BindingDescriptor = Montage.create(Montage, /** }, serializeSelf: {value: function(serializer) { - serializer.setReference("boundObject", this.boundObject); - serializer.set("boundObjectPropertyPath", this.boundObjectPropertyPath); - serializer.set("oneway", this.oneway); - serializer.set("deferred", this.deferred); - serializer.set("converter", this.converter); + var serialization = {}; + + serializer.addObjectReference(this.boundObject); + serialization[this.oneway ? "<-" : "<<->"] = "@" + serializer.getObjectLabel(this.boundObject) + "." + this.boundObjectPropertyPath; + serialization.deferred = this.deferred; + serialization.converter = this.converter; + + return serialization; }} }); @@ -1200,10 +1204,36 @@ Serializer.defineSerializationUnit("bindings", function(object) { } }); -Deserializer.defineDeserializationUnit("bindings", function(object, bindings) { - var sourcePath; - for (sourcePath in bindings) { - Object.defineBinding(object, sourcePath, bindings[sourcePath]); +Deserializer.defineDeserializationUnit("bindings", function(object, bindings, deserializer) { + for (var sourcePath in bindings) { + var binding = bindings[sourcePath], + dotIndex; + + if (!("boundObject" in binding)) { + var targetPath = binding["<-"] || binding["->"] || binding["<->>"] || binding["<<->"]; + + if (targetPath[0] !== "@") { + logger.error("Invalid binding syntax '" + targetPath + "', should be in the form of '@label.path'."); + throw "Invalid binding syntax '" + targetPath + "'"; + } + if ("->" in binding || "<->>" in binding) { + binding.boundObject = object; + binding.boundObjectPropertyPath = sourcePath; + + dotIndex = targetPath.indexOf("."); + object = deserializer.getObjectByLabel(targetPath.slice(1, dotIndex)); + sourcePath = targetPath.slice(dotIndex+1); + } else { + dotIndex = targetPath.indexOf("."); + binding.boundObject = deserializer.getObjectByLabel(targetPath.slice(1, dotIndex)); + binding.boundObjectPropertyPath = targetPath.slice(dotIndex+1); + } + + if ("<-" in binding || "->" in binding) { + binding.oneway = true; + } + } + Object.defineBinding(object, sourcePath, binding); } }); diff --git a/node_modules/montage/core/event/event-manager.js b/node_modules/montage/core/event/event-manager.js index bef8a67c..75272f85 100755 --- a/node_modules/montage/core/event/event-manager.js +++ b/node_modules/montage/core/event/event-manager.js @@ -103,13 +103,12 @@ var EventListenerDescriptor = Montage.create(Montage, { } }); -Serializer.defineSerializationUnit("listeners", function(object) { +Serializer.defineSerializationUnit("listeners", function(object, serializer) { var eventManager = defaultEventManager, uuid = object.uuid, eventListenerDescriptors = [], descriptors, descriptor, - listenerDescriptor, listener; for (var type in eventManager.registeredEventListeners) { @@ -119,12 +118,11 @@ Serializer.defineSerializationUnit("listeners", function(object) { for (var listenerUuid in descriptor.listeners) { listener = descriptor.listeners[listenerUuid]; - eventListenerDescriptor = EventListenerDescriptor.create(); - eventListenerDescriptor.type = type; - eventListenerDescriptor.listener = listener.listener; - eventListenerDescriptor.capture = listener.capture; - - eventListenerDescriptors.push(eventListenerDescriptor); + eventListenerDescriptors.push({ + type: type, + listener: serializer.addObjectReference(listener.listener), + capture: listener.capture + }); } } } diff --git a/node_modules/montage/core/logger.js b/node_modules/montage/core/logger.js index 6f90fab3..26094e76 100755 --- a/node_modules/montage/core/logger.js +++ b/node_modules/montage/core/logger.js @@ -148,6 +148,7 @@ Logger = exports.Logger = Montage.create(Montage,/** @lends module:montage/core/ for (i = 0; (args = buffer[i]); i++) { console.debug.apply(console, args); } + this.buffer.length = 0; } }, /** diff --git a/node_modules/montage/core/promise-connection.js b/node_modules/montage/core/promise-connection.js new file mode 100644 index 00000000..dc218f11 --- /dev/null +++ b/node_modules/montage/core/promise-connection.js @@ -0,0 +1,285 @@ + +var Montage = require("./core").Montage; +var UUID = require("./uuid"); +var Promise = require("./promise").Promise; +var PromiseQueue = require("./promise-queue").PromiseQueue; +var CacheMap = require("./shim/structures").CacheMap; +var logger = require("./logger").logger("promise-connections"); +var rootId = ""; + +exports.connect = connect; +function connect(port, local, options) { + return PromiseConnection.create().init(port, local, options); +} + +var PromiseConnection = +exports.PromiseConnection = Montage.create(Montage, { + + init: { + value: function (port, local, options) { + options = options || {}; + // sigil for debug message prefix + this.sigil = Math.random().toString(36).toUpperCase().slice(2, 4); + this.makeId = options.makeId || UUID.generate; + this.locals = CacheMap(options.max); + this.port = Adapter.adapt(port, options.origin); + this.port.forEach(function (message) { + this.log("receive:", message); + message = JSON.parse(message); + if (!this.receivers[message.type]) + return; + if (!this.locals.has(message.to)) + return; + this.receivers[message.type].call(this, message); + }, this); + this.makeLocal(rootId); + this.resolveLocal(rootId, local); + return this.makeRemote(rootId); + } + }, + + log: { + value: function (/*...args*/) { + logger.debug.apply(logger, ["Connection:", this.sigil].concat(Array.prototype.slice.call(arguments))) + } + }, + + encode: { + value: function (object) { + if (Promise.isPromise(object)) { + var id = this.makeId(); + this.makeLocal(id); + this.resolveLocal(id, object); + return {"@": id}; + } else if (Array.isArray(object)) { + return object.map(this.encode, this); + } else if (typeof object === "object") { + var newObject = {}; + if (has.call(object, key)) { + var newKey = key; + if (/^[!@]$/.exec(key)) + newKey = key + key; + newObject[newKey] = this.encode(object[key]); + } + return newObject; + } else { + return object; + } + } + }, + + decode: { + value: function (object) { + if (!object) { + return object; + } else if (object['!']) { + return Promise.reject(object['!']); + } else if (object['@']) { + return this.makeRemote(object['@']); + } else if (Array.isArray(object)) { + return object.map(this.decode, this); + } else if (typeof object === 'object') { + var newObject = {}; + for (var key in object) { + if (has.call(object, key)) { + var newKey = key; + if (/^(@@|!!)$/.exec(key)) + newKey = key.substring(1); + newObject[newKey] = this.decode(object[key]); + } + } + return newObject; + } else { + return object; + } + } + }, + + makeLocal: { + value: function (id) { + if (!this.locals.has(id)) { + this.locals.set(id, Promise.defer()); + } + return this.locals.get(id).promise; + } + }, + + resolveLocal: { + value: function (id, value) { + this.log('resolve:', 'L' + JSON.stringify(id), JSON.stringify(value)); + this.locals.get(id).resolve(value); + } + }, + + makeRemote: { + value: function (id) { + return RemotePromise.create().init(this, id); + } + }, + + receivers: { + value: Object.create(null, { + + resolve: { + value: function (message) { + if (this.locals.has(message.to)) { + this.resolveLocal(message.to, this.decode(message.resolution)); + } + } + }, + + send: { + value: function (message) { + var connection = this; + this.locals.get(message.to).promise + .send(message.op, this.decode(message.args)) + .then(function (resolution) { + return Promise.call(function () { + return JSON.stringify({ + type: 'resolve', + to: message.from, + resolution: connection.encode(resolution) + }) + }) + .fail(function (error) { + console.log('XXX:', error); + return JSON.stringify({ + type: 'resolve', + to: message.from, + resolution: null + }); + }) + }, function (reason, error, rejection) { + return Promise.call(function () { + return JSON.stringify({ + type: 'resolve', + to: message.from, + resolution: {'!': connection.encode(reason)} + }) + }) + .fail(function () { + return JSON.stringify({ + type: 'resolve', + to: message.from, + resolution: {'!': null} + }); + }) + }) + .then(function (envelope) { + connection.port.put(envelope); + }) + .end(); + } + } + + }) + } + +}); + +var RemotePromise = Montage.create(Promise.AbstractPromise, { + init: { + value: function (connection, id) { + this._connection = connection; + this._id = id; + this.Promise = Promise; + return this; + } + }, + _handlers: { + value: {} + }, + _fallback: { + value: function (resolve, op /*...args*/) { + var localId = this._connection.makeId(); + var response = this._connection.makeLocal(localId); + var args = Array.prototype.slice.call(arguments, 2); + this._connection.log('sending:', 'R' + JSON.stringify(this._id), JSON.stringify(op), JSON.stringify(args)); + this._connection.port.put(JSON.stringify({ + type: 'send', + to: this._id, + from: localId, + op: op, + args: this._connection.encode(args) + })); + return response; + } + } +}); + +var Adapter = +exports.Adapter = Montage.create(Montage, { + adapt: { + value: function (port, origin) { + return this.create().init(port, origin); + } + }, + init: { + value: function (port, origin) { + if (port.postMessage) { + // MessagePorts + this._send = function (message) { + // some message ports require an "origin" + port.postMessage(message, origin); + }; + } else if (port.send) { + // WebSockets have a "send" method, indicating + // that we cannot send until the connection has + // opened. We change the send method into a + // promise for the send method, resolved after + // the connection opens, rejected if it closes + // before it opens. + var deferred = Promise.defer(); + this._send = deferred.promise; + port.addEventListener("open", function () { + deferred.resolve(port.send); + }); + port.addEventListener("close", function () { + queue.close(); + deferred.reject("Connection closed."); + }); + } else if (port.get && port.put) { + return port; + } else { + throw new Error("An adaptable message port required"); + } + // Message ports have a start method; call it to make sure + // that messages get sent. + port.start && port.start(); + var queue = this._queue = PromiseQueue.create().init(); + function onmessage(event) { + queue.put(event.data); + } + if (port.addEventListener) { + port.addEventListener("message", onmessage, false); + } else { + // onmessage is one thing common between WebSocket and + // WebWorker message ports. + port.onmessage = onmessage; + } + this._port = port; + this.closed = queue.closed; + return this; + } + }, + get: { + value: function () { + return this._queue.get(); + } + }, + put: { + value: function (value) { + return Promise.invoke(this._send, "call", this._port, value); + } + }, + forEach: { + value: PromiseQueue.forEach + }, + close: { + value: function (reason, error, rejection) { + this._port.close && this._port.close(); + return this._queue.close(); + } + } +}); + diff --git a/node_modules/montage/core/promise-queue.js b/node_modules/montage/core/promise-queue.js new file mode 100644 index 00000000..11eeb788 --- /dev/null +++ b/node_modules/montage/core/promise-queue.js @@ -0,0 +1,65 @@ + +var Montage = require("./core").Montage; +var Promise = require("./promise").Promise; + +exports.PromiseQueue = Montage.create(Montage, { + init: { + value: function () { + this._ends = Promise.defer(); + this._closed = Promise.defer(); + this.closed = this._closed.promise; + return this; + } + }, + put: { + value: function (value) { + var next = Promise.defer(); + this._ends.resolve({ + head: value, + tail: next.promise + }); + this._ends.resolve = function (resolution) { + next.resolve(resolution); + }; + } + }, + get: { + value: function () { + var ends = this._ends; + var result = ends.promise.get("head"); + this._ends = { + resolve: function (resolution) { + ends.resolve(resolution); + }, + promise: ends.promise.get("tail") + }; + return result.fail(function (reason, error, rejection) { + this._closed.resolve(); + return rejection; + }); + } + }, + close: { + value: function (reason, error, rejection) { + var end = { + head: rejections || Promise.reject(reason, error) + }; + end.tail = end; + this._ends.resolve(end); + return this._closed.promise; + } + }, + forEach: { + value: function (put, thisp) { + var queue = this; + function loop() { + return queue.get().then(function (value) { + put.call(thisp, value); + }) + .then(loop); + } + return loop(); + } + } +}); + diff --git a/node_modules/montage/core/promise.js b/node_modules/montage/core/promise.js index eb9ccd86..7563a742 100755 --- a/node_modules/montage/core/promise.js +++ b/node_modules/montage/core/promise.js @@ -127,6 +127,7 @@ var PrimordialPromise = Creatable.create({ // automatically subcreate each of the contained promise types var creation = Object.create(this); + creation.AbstractPromise = this.AbstractPromise.create(promiseDescriptor); creation.DeferredPromise = this.DeferredPromise.create(promiseDescriptor); creation.FulfilledPromise = this.FulfilledPromise.create(promiseDescriptor); creation.RejectedPromise = this.RejectedPromise.create(promiseDescriptor); @@ -156,7 +157,14 @@ var PrimordialPromise = Creatable.create({ } }, + // deprecated ref: { + get: function () { + return this.resolve; + } + }, + + resolve: { value: function (object) { // if it is already a promise, wrap it to guarantee // the full public API of this promise variety. @@ -253,7 +261,8 @@ var PrimordialPromise = Creatable.create({ self._reason = reason; self._error = error; self.Promise = this; - errors.push(error && error.stack || self); + rejections.push(self); + errors.push(error ? (error.stack ? error.stack : error) : reason); return self; } }, @@ -266,8 +275,9 @@ var PrimordialPromise = Creatable.create({ then: function (r, o, rejected) { // remove this error from the list of unhandled errors on the console if (rejected) { - var at = errors.indexOf(this._error && this._error.stack || this); + var at = rejections.indexOf(this); if (at !== -1) { + rejections.splice(at, 1); errors.splice(at, 1); } } @@ -378,6 +388,10 @@ var PrimordialPromise = Creatable.create({ } }) + }, + + AbstractPromise: { + value: AbstractPromise } }); @@ -411,7 +425,7 @@ var Promise = PrimordialPromise.create({}, { // Descriptor for each of the three try { deferred.resolve(fulfilled ? fulfilled(value) : value); } catch (error) { - console.error(error.stack); + console.error(error.stack || error, fulfilled); deferred.reject(error.message, error); } } @@ -556,18 +570,21 @@ var Promise = PrimordialPromise.create({}, { // Descriptor for each of the three delay: { value: function (timeout) { - var deferred = this.Promise.defer(); - this.then(function (value) { - clearTimeout(handle); - deferred.resolve(value); - }, function (reason, error, rejection) { - clearTimeout(handle); - deferred.resolve(rejection); + var self = this; + var promise; + if (arguments.length === 0) { + timeout = this; + } else { + promise = this; + } + return Promise.ref(timeout) + .then(function (timeout) { + var deferred = self.Promise.defer(); + setTimeout(function () { + deferred.resolve(promise); + }, timeout); + return deferred.promise; }); - var handle = setTimeout(function () { - deferred.reject("Timed out"); - }, timeout); - return deferred.promise; } }, @@ -582,7 +599,7 @@ var Promise = PrimordialPromise.create({}, { // Descriptor for each of the three deferred.resolve(rejection); }).end(); var handle = setTimeout(function () { - deferred.reject("Timed out"); + deferred.reject("Timed out", new Error("Timed out")); }, timeout); return deferred.promise; } @@ -657,6 +674,7 @@ var Promise = PrimordialPromise.create({}, { // Descriptor for each of the three }); +var rejections = []; var errors = []; // Live console objects are not handled on tablets if (typeof window !== "undefined" && !window.Touch) { diff --git a/node_modules/montage/core/serializer.js b/node_modules/montage/core/serializer.js index 28f1b1d8..74080770 100755 --- a/node_modules/montage/core/serializer.js +++ b/node_modules/montage/core/serializer.js @@ -28,7 +28,7 @@ if (typeof window !== "undefined") { */ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Serializer# */ { _MONTAGE_ID_ATTRIBUTE: {value: "data-montage-id"}, - _serializedObjects: {value: {}}, // uuid -> string + _serializedObjects: {value: {}}, // label -> string _serializedReferences: {value: {}}, // uuid -> string _externalObjects: {value: null}, // label -> object _externalElements: {value: null}, @@ -38,6 +38,7 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se _objectNamesIndex: {value: null}, _objectLabels: {value: null}, // uuid -> label _serializationUnits: {value: []}, + _serializationUnitsIndex: {value: {}}, serializeNullValues: {value: false}, @@ -48,7 +49,7 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se @param {function} funktion The delegate function that creates the serialization unit. This function accepts the object being serialized as an argument and should return an object to be be JSON'd. */ defineSerializationUnit: {value: function(name, funktion) { - this._serializationUnits.push({ + this._serializationUnits.push(this._serializationUnitsIndex[name] = { name: name, funktion: funktion }); @@ -120,61 +121,117 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se }, /** - This function is to be used in the context of serializeSelf delegate used for custom object serializations. + This function is to be used in the context of serializeProperties delegate used for custom object serializations. It adds an entry to the "properties" serialization unit of the object being serialized. @function @param {string} name The name of the entry to be added. @param {string} value The value to be serialized. */ - set: {value: function(name, value) { - var stack = this._objectStack; - - return (stack[stack.length - 1][name] = value); - }}, - - /** - This function is to be used in the context of serializeSelf delegate used for custom object serializations. - It adds an entry to the "properties" serialization unit of the object being serialized. The value for this entry will be stored as a reference only and not the value itself. - @function - @param {string} name The name of the entry to be added. - @param {string} value The value to be referenced. - */ - setReference: {value: function(name, value) { + set: {value: function(name, value, type) { var stack = this._objectStack, stackElement = stack[stack.length - 1], - objectReferences = this._objectReferences, - uuid = stackElement.uuid; + objectReferences, uuid; - if (!(uuid in objectReferences)) { - objectReferences[uuid] = {}; + stackElement[name] = value; + if (type === "reference") { + uuid = stackElement.uuid; + objectReferences = this._objectReferences; + if (!(uuid in objectReferences)) { + objectReferences[uuid] = {}; + } objectReferences[uuid][name] = true; } - - return (stackElement[name] = value); }}, /** - This function is to be used in the context of serializeSelf delegate used for custom object serializations. + This function is to be used in the context of serializeProperties delegate used for custom object serializations. It serializes all properties specified as part of the "properties" serialization unit. @function @param {array} propertyNames The array with the property names to be serialized. */ - setProperties: {value: function(propertyNames) { + setAll: {value: function(propertyNames) { var ix = this._objectStack.length - 2, object = this._objectStack[ix]; + if (!propertyNames) { + propertyNames = Montage.getSerializablePropertyNames(object); + } + for (var i = 0, l = propertyNames.length; i < l; i++) { var propertyName = propertyNames[i]; - if (Montage.getPropertyAttribute(object, propertyName, "serializable") === "reference") { - this.setReference(propertyName, object[propertyName]); - } else { - this.set(propertyName, object[propertyName]); + this.set(propertyName, object[propertyName], Montage.getPropertyAttribute(object, propertyName, "serializable")); + } + }}, + + setProperty: { + value: function(name, value, type) { + var stack = this._objectStack, + stackElement = stack[stack.length - 1], + objectReferences, uuid; + + stackElement.properties[name] = value; + if (type === "reference") { + objectReferences = this._objectReferences, + uuid = stackElement.properties.uuid; + if (!(uuid in objectReferences)) { + objectReferences[uuid] = {}; + } + objectReferences[uuid][name] = true; } } + }, + + setProperties: {value: function(propertyNames) { + var ix = this._objectStack.length - 2, + object = this._objectStack[ix]; + + if (!propertyNames) { + propertyNames = Montage.getSerializablePropertyNames(object); + } + + for (var i = 0, l = propertyNames.length; i < l; i++) { + var propertyName = propertyNames[i]; + this.setProperty(propertyName, object[propertyName], Montage.getPropertyAttribute(object, propertyName, "serializable")); + } }}, + setType: { + value: function(type, value) { + if (type === "object" || type === "prototype" || type === "value") { + var stack = this._objectStack, + stackElement = stack[stack.length - 1]; + + delete stackElement.prototype; + delete stackElement.object; + delete stackElement.value; + stackElement[type] = value; + } + } + }, + + setUnit: { + value: function(name) { + var stack = this._objectStack, + stackElement = stack[stack.length - 1]; + + if (stackElement._units.indexOf(name) === -1) { + stackElement._units.push(this._serializationUnitsIndex[name]); + } + } + }, + + setAllUnits: { + value: function() { + var stack = this._objectStack, + stackElement = stack[stack.length - 1]; + + stackElement._units.length = 0; + stackElement._units.push.apply(stackElement._units, this._serializationUnits); + } + }, + /** - This function is to be used in the context of serializeSelf delegate used for custom object serializations. + This function is to be used in the context of serializeProperties delegate used for custom object serializations. It adds an object to be serialized into the current serialization. @function @param {object} object The object to be serialized. @@ -188,6 +245,23 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se } }}, + addObjectReference: { + value: function(object) { + var label = this._getObjectLabel(object); + + if (!this._serializedObjects[label]) { + this._externalObjects[label] = object; + } + return {"@": label}; + } + }, + + getObjectLabel: { + value: function(object) { + return this._getObjectLabel(object); + } + }, + /** @private */ @@ -216,7 +290,7 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se for (var label in externalObjects) { var object = externalObjects[label]; - if (this._serializedObjects[object.uuid]) { + if (this._serializedObjects[label]) { delete externalObjects[label]; } } @@ -227,7 +301,7 @@ var Serializer = Montage.create(Montage, /** @lends module:montage/serializer.Se /** Returns a list of the external elements that were referenced in the last serialization