Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Deprecate mix/merge and merge the logic into set, use 5 arg signature…

… everywhere

this.set(undefined, value) now merges object into this
this.watch(undefined, callback) now observes all values in an object
Add _join callback to be called instead of _hash when watch or set is called without a key
Objects are now live-merged by default
Remove all the inline docs. They were useless and badly written, unfortunately.
Fix Cookies bug
Move dynamic properties assignment to Struct.
  • Loading branch information...
commit eecc2de6b64ba0786effc4f4d270faa8dbc5dad1 1 parent c6ed8d2
Yaroslaff Fedin authored
View
28 Source/DOM/Element.js
@@ -87,7 +87,7 @@ LSD.Element = new LSD.Struct({
if (typeof value == 'string')
value = typeof roles[value] == 'undefined' ? roles.get(value) : roles[value];
if (typeof old == 'string') old = roles[old] || undefined;
- this.mix(undefined, value, old, meta, false, true);
+ this.mix(undefined, value, old, meta, 'under');
}
},
chunked: true
@@ -397,9 +397,10 @@ LSD.Element = new LSD.Struct({
if (classes && classes._name != element.className)
element.className = classes._name;
}
- this.set('element', element)
+ this._set('element', element)
}
- this.mix('childNodes.built', value, old, meta);
+ if (value || old)
+ this._set('childNodes.built', value, old, meta);
},
/*
Javascript DOM is known for its unfriendly implementation of accessibility
@@ -427,8 +428,8 @@ LSD.Element = new LSD.Struct({
environments, in focus-driven navigation and lots of other applications.
*/
focused: function(value, old, meta) {
- if (meta === this) return;
- this.mix('parentNode.focused', value, old, meta || this);
+ if (meta === this || (!value && !old)) return;
+ this.set('parentNode.focused', value, old, meta || this);
if (value && !meta && this.ownerDocument)
this.ownerDocument.change('activeElement', this, undefined, false);
},
@@ -558,7 +559,7 @@ LSD.Element = new LSD.Struct({
if (meta === 'variables') return;
for (var child, i = 0, children = this.childNodes; child = children[i++];)
if (child.nodeType != 3 && (!child.fragment || child.fragment == this.fragment))
- child.mix('variables', value, old, this, true);
+ child.mix('variables', value, old, this);
},
multiple: function(value, old) {
if (value) {
@@ -641,8 +642,7 @@ LSD.Element = new LSD.Struct({
element.
*/
microdata: function(value, old, meta) {
- if ((value && !this.variables) || (old && old == this.variables))
- this.mix('variables', value, old, meta, true);
+ this.mix('variables', value, old, meta);
},
itemscope: function(value, old, meta) {
value = value && this._construct('microdata') || undefined;
@@ -651,9 +651,9 @@ LSD.Element = new LSD.Struct({
},
itemprop: function(value, old, meta) {
if (value)
- this.mix('parentNode.microdata.' + value, this, undefined, meta, true);
+ this._set('parentNode.microdata.' + value, this, undefined, meta);
if (old)
- this.mix('parentNode.microdata.' + old, undefined, this, meta, true);
+ this._set('parentNode.microdata.' + old, undefined, this, meta);
},
itemtype: function(value, old) {
@@ -944,7 +944,7 @@ LSD.Document.prototype.mix('states', {
invoked: ['invoke', 'revoke']
})
-LSD.Element.prototype.set('built', false);
-LSD.Element.prototype.set('hidden', false);
-LSD.Element.prototype.set('disabled', false);
-LSD.Element.prototype.set('focused', false);
+LSD.Element.prototype._set('built', false);
+LSD.Element.prototype._set('hidden', false);
+LSD.Element.prototype._set('disabled', false);
+LSD.Element.prototype._set('focused', false);
View
2  Source/DOM/Node.js
@@ -67,7 +67,7 @@ LSD.Node.prototype.setVariables = function(value, old, meta) {
this.mix('variables',
value && (fragment && fragment != value.fragment && fragment.variables || value.get('variables', true, 'variables')),
old && (fragment && fragment != old.fragment && fragment || old).variables,
- 'variables', true);
+ 'variables');
};
LSD.Node.prototype.$family = function() {
return 'widget';
View
6 Source/DOM/NodeList.js
@@ -30,7 +30,7 @@ LSD.NodeList = function() {
return collection;
} else {
if (this._sortBy)
- this.watch(this._observeIndex);
+ this.watch(undefined, this._observeIndex);
return LSD.Array.apply(this, arguments);
}
}
@@ -73,8 +73,8 @@ LSD.NodeList.prototype.fn = function(collection, key, value, old, meta) {
};
LSD.Relation = new LSD.Struct({
- match: '_owner.matches.set manager',
- proxy: '_owner.proxies.set manager'
+ match: '_owner.matches manager',
+ proxy: '_owner.proxies manager'
}, 'NodeList');
LSD.Relation.prototype._aggregate = true;
LSD.Relation.prototype._object = false;
View
4 Source/DOM/Textnode.js
@@ -24,8 +24,6 @@ LSD.Textnode = LSD.Struct({
textContent: function(value, old, meta) {
if (typeof value != 'undefined') {
value = String(value)
- if ((meta && meta._calculated) || value === '')
- debugger
if (!meta || !(meta.push || meta._calculated)) {
for (var previous = -1, start, end, bits, substr; (start = value.indexOf('${', previous + 1)) > -1;) {
if ((end = value.indexOf('}', start)) == -1) continue;
@@ -78,7 +76,7 @@ LSD.Textnode.prototype.__initialize = function() {
this.fragment = arg;
break;
default:
- this.mix(undefined, arg);
+ this._mix(undefined, arg);
}
}
}
View
1  Source/Properties/Attributes.js
@@ -45,7 +45,6 @@ LSD.Properties.Attributes.prototype.__cast = function(key, value, old, meta) {
if (((!meta || meta !== 'states') && ns.states[key]) || owner._properties[key])
owner.set(key, value, old, 'attributes');
if (key.substr(0, 5) == 'data-') {
- if (value === undefined) debugger
owner.mix('variables.' + key.substring(5), value, old, meta);
}
if (owner.matches) {
View
10 Source/Properties/Matches.js
@@ -109,7 +109,7 @@ LSD.Properties.Matches.prototype.__cast = function(key, value, old, meta, extra,
if (vdef) {
hash.push([key, value, stateful]);
if (this._results) {
- var group = this._hash(key, null, null, null, this._results);
+ var group = this.__hash(key, null, null, null, null, this._results);
for (var i = 0, widget; widget = group[i++];) {
if (!stateful) {
if (typeof value == 'function') value(widget);
@@ -124,7 +124,7 @@ LSD.Properties.Matches.prototype.__cast = function(key, value, old, meta, extra,
if (hash) for (var i = hash.length, fn; i--;) {
if ((fn = hash[i]) && (fn = fn[1]) && (fn === old || fn.callback === old)) {
if (this._results) {
- var group = this._hash(key, null, null, null, this._results);
+ var group = this.__hash(key, null, null, null, null, this._results);
for (var j = 0, result; result = group[j++];) {
if (!stateful) {
if (typeof fn == 'function') fn(undefined, old);
@@ -172,7 +172,7 @@ LSD.Properties.Matches.prototype._advancer = function(call, value, old) {
the array of expression from left to right and handles each expression separately
storing a callback that advances the selector to the next expression.
*/
-LSD.Properties.Matches.prototype._hash = function(key, value, old, meta, storage) {
+LSD.Properties.Matches.prototype.__hash = function(key, value, old, meta, mode, storage) {
if (typeof key == 'string') {
if (this._nonenumerable[key]) return;
var parsed = this._parsed;
@@ -275,5 +275,5 @@ LSD.Properties.Matches.prototype.remove = function(combinator, tag, value, wildc
}
LSD.Properties.Matches.prototype._types = {pseudos: 1, classes: 1, attributes: 1};
LSD.Properties.Matches.prototype._parsed = {};
-LSD.Properties.Matches.prototype._composite = false;
-LSD.Properties.Matches.prototype._watchable = null;
+LSD.Properties.Matches.prototype._parser = Slick.parse;
+LSD.Properties.Matches.prototype._composite = true;
View
4 Source/Properties/Microdata.js
@@ -25,6 +25,7 @@ LSD.Properties.Microdata.prototype.__cast = function(key, value, old, meta) {
if (meta !== 'microdata' && meta !== 'textContent') {
if (!this._elements) return;
var element = this._elements[key];
+ if (element == null) return;
var storage = this._values;
if (!storage) storage = this._values = {};
if (odef && old !== storage[key]) odef = old = undefined;
@@ -35,6 +36,8 @@ LSD.Properties.Microdata.prototype.__cast = function(key, value, old, meta) {
}
}
LSD.Properties.Microdata.prototype.___hash = function(key, value, old, meta) {
+ if (this._nonenumerable[key])
+ return;
if (value && value.lsd) {
var storage = this._elements || (this._elements = {});
var group = storage[key];
@@ -59,6 +62,7 @@ LSD.Properties.Microdata.prototype.___hash = function(key, value, old, meta) {
return true;
}
LSD.Properties.Microdata.prototype._shared = true;
+LSD.Properties.Microdata.prototype._owning = false;
LSD.Properties.Microdata.prototype._trigger = 'lsd';
LSD.Properties.Microdata.prototype._nonenumerable = LSD.Struct.implement(LSD.Properties.Microdata.prototype._nonenumerable, {
_values: true,
View
4 Source/Properties/Proxies.js
@@ -34,10 +34,10 @@ LSD.Properties.Proxies.prototype._hash = function(key) {
// return this.content || this.content
case '_owner':
return;
- default:
+ default:
if (typeof key == 'string') {
var object = this._selectors || (this._selectors = {});
- } else if (key && key.exec) {
+ } else if (key.exec) {
var object = this._wildcards || (this._wildcards = {});
var regexes = this._regexes || (this._regexes = {});
if (!regexes[key]) regexes[key] = key;
View
2  Source/Properties/Request.js
@@ -119,7 +119,7 @@ LSD.Request.prototype.isSuccess = function() {
return this.status > 199 && this.status < 300;
};
LSD.Request.prototype.___hash = function(key) {
- debugger
+ if (typeof key != 'string') return;
var first = key.charAt(0);
if (first != '_' && first === first.toUpperCase())
return 'headers.' + key;
View
66 Source/Script/Script.js
@@ -51,9 +51,8 @@ provides:
LSD.Script = function(input, scope, output) {
var regex = this._regexp, type = typeof input;
if (regex) {
- if (scope) {
+ if (scope)
this.scope = scope;
- }
if (output)
if (output.nodeType == 9) this.document = output;
else this.output = output;
@@ -93,7 +92,7 @@ LSD.Script = function(input, scope, output) {
if (!this.proto && this.locals) this.findLocals(this.locals);
if (this.locals) {
this.variables = new LSD.Journal;
- if (this.scope) this.mix('variables', this.scope.variables || this.scope, undefined, undefined, true);
+ if (this.scope) this.mix('variables', this.scope.variables || this.scope, undefined, undefined, 'under');
this.parentScope = this.scope;
this.scope = this;
}
@@ -208,11 +207,11 @@ LSD.Script.prototype = new (LSD.Struct({
self.value = value;
}
this._enumerated = true;
- value.watch(this._enumerator);
+ value.watch(undefined, this._enumerator);
}
}
if (old != null && old.push && old._watch && this._enumerated) {
- old.unwatch(this._enumerator);
+ old.unwatch(undefined, this._enumerator);
delete this._enumerated;
}
var output = this.output;
@@ -237,7 +236,7 @@ LSD.Script.prototype = new (LSD.Struct({
break;
default:
if (output.push) this._callback(output, null, value, old, this);
- else this._callback(output, value, null, old);
+ else this._callback(output, null, value, old);
}
}
}
@@ -298,51 +297,9 @@ LSD.Script.prototype = new (LSD.Struct({
this.execute(!!value, meta);
}
},
-
-/*
- Selectors can be used without escaping them in strings in LSD.Script.
- A selector targetted at widgets updates the collection as the widgets
- change and recalculates the expression in real time.
-
- The only tricky part is that a simple selector may be recognized as
- a variable (e.g. `div.container`) or logical expression (`ul > li`) and
- not fetch the elements. A combinator added before ambigious expression
- would help parser to recognize selector. Referential combinators
- `$`, `&`, `&&`, and `$$` may be used for that. Selectors are targetted
- at widgets by default, unless `$$` or `$` combinator is used.
-
- You can learn more about selectors and combinators in LSD.Module.Selector
-
- Examples of expressions with selectors:
-
- // Following selectors will observe changes in DOM and update collection
- // Because they are targetted at widgets
-
- // Count `item` children in `menu#main` widget
- "count(menu#main > item)"
-
- // Returns collection of widgets related to `grid` as `items` that are `:selected`
- "grid::items:selected"
-
- // Return next widget to current widget
- "& + *"
-
- // Combinators that have $ or $$ as referential combinators will not observe changes
- // and only fetch element once from Element DOM
-
- // Find all `item` children in `menu` in current element
- "$ menu > item"
-
- // Find `section` in parents that has no `section` siblings, and a details element next to it
- "$ ! section:only-of-type() + details"
-
- // Following example is INCORRECT, because it is AMBIGIOUS and will not be recognized selector
- "ul > li" // variable `ul` greater than `li`
-
- // CORRECT way: Add a combinator to disambiguate
- "& ul > li"
-
-*/
+ wrapper: function() {
+
+ },
selector: function(value, old) {
console.log(value, old)
},
@@ -629,14 +586,11 @@ LSD.Script.prototype.execute = function(value, meta) {
case true:
break;
case false:
- debugger
for (var k = i + 1; k < j; k++) {
var argument = this.args[k];
if (argument != null && argument._calculated && argument.attached)
-
- debugger
- if (argument != null && argument._calculated && argument.attached)
- argument._set('attached')
+ if (argument.executed)
+ argument.execute(false, false)
}
args = args[args.length - 1];
break loop;
View
63 Source/Type/Array.js
@@ -76,8 +76,30 @@ LSD.Array.prototype._offset = 0;
LSD.Objects, thus it does not affect their `._owner` link.
*/
LSD.Array.prototype._owning = false;
-LSD.Array.prototype._hash = function(key, value, old, meta, from, i) {
- if (arguments.length < 6) return;
+LSD.Array.prototype._join = function(key, value, old, meta, from) {
+ if (from != 'watch') return;
+ if (value) {
+ for (var i = 0, j = this._length >>> 0; i < j; i++)
+ if (value._object)
+ this._callback(value, i, this[i], false);
+ else
+ this._callback(value, this[i], i, false);
+ (this.__watchers || (this.__watchers = [])).push(value);
+ }
+ if (old) {
+ for (var i = 0, j = this._length >>> 0; i < j; i++)
+ if (old._object)
+ this._callback(old, i, this[i], false);
+ else
+ this._callback(old, this[i], i, false);
+ var watchers = this.__watchers;
+ var index = watchers.indexOf(old);
+ watchers.splice(index, 1);
+ }
+ return true;
+}
+LSD.Array.prototype._hash = function(key, value, old, meta, from) {
+ if (from == 'get' || (key.indexOf && key.indexOf('.') > -1)) return;
var index = parseInt(key);
if (index != key) return;
old = this[index];
@@ -141,8 +163,9 @@ LSD.Array.prototype.push = function() {
return this._length;
};
LSD.Array.prototype.indexOf = function(object, from) {
- for (var method = '_hash'; hash === undefined && this[method]; method = '_' + method)
- var hash = this[method](hash || object);
+ if (object != null)
+ for (var method = '_hash'; hash === undefined && this[method]; method = '_' + method)
+ var hash = this[method](hash || object, undefined, undefined, undefined, 'get');
var length = this._length >>> 0;
for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
var value = this[i];
@@ -308,26 +331,6 @@ LSD.Array.prototype.unshift = function() {
this.splice.apply(this, [0, 0].concat(Array.prototype.slice.call(arguments, 0)))
return this._length;
};
-LSD.Array.prototype.watch = function(callback, fn, meta) {
- if (typeof fn != 'undefined') return this._watch(callback, fn);
- for (var i = 0, j = this._length >>> 0; i < j; i++)
- if (callback._object)
- this._callback(callback, i, this[i], false);
- else
- this._callback(callback, this[i], i, false);
- (this.__watchers || (this.__watchers = [])).push(callback);
-};
-LSD.Array.prototype.unwatch = function(callback, fn, meta) {
- if (typeof fn != 'undefined') return this._watch(callback, fn);
- for (var i = 0, j = this._length >>> 0; i < j; i++)
- if (callback._object)
- this._callback(callback, i, this[i], false);
- else
- this._callback(callback, this[i], i, false);
- var watchers = this.__watchers;
- var index = watchers.indexOf(callback);
- watchers.splice(index, 1);
-};
LSD.Array.prototype._seeker = function(call, index, value, old, meta, from) {
var block = call.block, invoker = call.invoker, array = call.meta, length = array && array.length;
if (value !== undefined) {
@@ -427,7 +430,7 @@ LSD.Array.prototype.sort = function(callback, plain) {
if (!callback) callback = this._sorter;
var sorted = plain ? [] : new LSD.Array;
var map = [];
- this.watch(function(value, index, old, meta, from) {
+ this.watch(undefined, function(value, index, old, meta, from) {
var moving = meta & 0x1;
if (value !== undefined) {
for (var i = sorted._length || sorted.length; i > 0; i--)
@@ -453,10 +456,16 @@ LSD.Array.prototype.sort = function(callback, plain) {
return sorted;
};
LSD.Array.prototype.limit = function(number) {
- return (this._origin ? this : (new this.constructor).mix('_origin', this)).mix('_limit', number);
+ var array = this._origin ? this : new this.constructor
+ array.set('_origin', this)
+ array.set('_limit', number);
+ return array;
};
LSD.Array.prototype.offset = function(number) {
- return (this._origin ? this : (new this.constructor).mix('_origin', this)).mix('_offset', number);
+ var array = this._origin ? this : new this.constructor
+ array.set('_origin', this)
+ array.set('_offset', number);
+ return array;
};
LSD.Array.prototype.every = function(callback) {
if (callback.result != null) return callback.result === 0;
View
5 Source/Type/Data.js
@@ -22,7 +22,7 @@ LSD.Data = function(object) {
if (typeof object == 'string')
subject.fromString(object);
else if (object)
- subject.mix(undefined, object);
+ subject._mix(undefined, object);
return subject;
};
LSD.Data.prototype = new LSD.Object;
@@ -92,4 +92,5 @@ LSD.Data.prototype._hash = function(key, value, old, meta) {
}
}
}
-LSD.Data.prototype._watchable = /^[a-zA-Z0-9._\[\]-]+?$/;
+
+LSD.Data.prototype._composite = true;
View
2  Source/Type/Group.js
@@ -27,7 +27,7 @@ LSD.Group = function(object, constructor) {
object = null;
}
if (constructor) this.__constructor = typeof constructor == 'string' ? LSD[constructor] : constructor;
- if (object != null) this.mix(undefined, object)
+ if (object != null) this._mix(undefined, object)
};
LSD.Group.prototype = new LSD.Object;
LSD.Group.prototype.constructor = LSD.Group;
View
108 Source/Type/Journal.js
@@ -49,35 +49,34 @@ provides:
*/
LSD.Journal = function(object) {
- if (object != null) this.mix(undefined, object)
+ if (object != null) this._mix(undefined, object)
};
LSD.Journal.prototype = new LSD.Object;
LSD.Journal.prototype.constructor = LSD.Journal;
-LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
- if (arguments.length < 6) return;
- if (typeof index != 'number')
- index = key.indexOf('.');
- if (index > -1) return;
-/*
- Most of hash table implementations have a simplistic way to delete
- a key - they just erase the value. LSD.Journal's unset function
- call may result in one of 3 cases
-
- * Value becomes undefined, like after a `delete object.key` call in
- javascript. It only happens if there was a single logged value by
- that key. Callbacks are called and passed that value as a second
- argument.
- * Value is reverted to previous value on the stack. Callbacks are
- fired with both new and old value as arguments.
- * Value does not change, if the value being unset was not on top of
- the stack. It may also happen if there were two identical values
- on top of the stack, so removing the top value falls back to the
- same value. Callbacks don't fire.
-*/
+LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, get) {
+ if (prepend == 'watch' || prepend == 'get' || key.indexOf('.') > -1) return;
var property = this._properties;
if (property && (property = property[key]) && property.journal === false)
return;
+ switch (typeof prepend) {
+ case 'number':
+ var position = prepend;
+ prepend = false;
+ break;
+ case 'string':
+ switch (prepend) {
+ case 'over': case 'under':
+ var val = value || old;
+ if (typeof val == 'object' && !val.exec && !val.push && !val.nodeType)
+ return;
+ prepend = prepend == 'under';
+ break;
+ default:
+ prepend = prepend == 'before';
+ break;
+ }
+ }
var journal = this._journal;
if (journal) {
var group = journal[key];
@@ -91,19 +90,10 @@ LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
old = group[k];
}
}
- if (typeof prepend == 'number') {
- var position = prepend;
- prepend = false;
- }
var chunked = property && property.chunked, current = this[key];
if (positioned == null) positioned = -1;
if (before) positioned ++;
if (after) positioned ++;
-/*
- When Journal setter is given an old value, it removes it from the
- journal. If a new value is not given and the old value was on top
- of the stack, it falls back to a previous value from the stack.
-*/
if (old !== undefined) {
var erasing = old === current;
if (j && position == null)
@@ -118,19 +108,6 @@ LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
if (old && old._calculated)
this._watch(key, undefined, old, meta)
} else old = current;
-/*
- Journal setters accept a position at which the value should be written in
- journal. Positioned values are inserted in the beginning of the stack before
- regular values and may only be unset with the same position argument. In
- addition to regular numerical indecies, setters accept Infinity and
- -Infinity positions that insert a value after/before other indexed values.
-
- That adds up to 5 distinct sections in the journal. Journal can have multiple
- indexed, prepended or appended values. But only one value for Infinity and
- one for -Infinity positions.
-
- [-Infinity, ... 0, 1, 2 .., Infinity, ... 'prepended' .., 'regular' ...]
-*/
if (position != null) {
if (!group) {
if (!journal) journal = this._journal = {};
@@ -154,12 +131,12 @@ LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
group.before = value;
}
var diff = position - positioned;
- if (diff > 0) {
+ if (diff > 0)
for (var i = j, k = positioned; --i > k;)
group[i + diff] = group[i];
- }
if (value !== undefined) {
- if (diff > 0) j += diff;
+ if (diff > 0)
+ j += diff;
group[position] = value;
} else
delete group[position];
@@ -173,7 +150,7 @@ LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
break;
} else if (k > position) return true;
if (k > -1)
- return this.set(key, value, undefined, meta, prepend, index, false);
+ return this._set(key, value, undefined, meta, undefined, false);
return;
}
if (value !== undefined) {
@@ -204,47 +181,16 @@ LSD.Journal.prototype._hash = function(key, value, old, meta, prepend, index) {
} else return;
} else if (old !== current)
return false;
- return this.set(key, value, undefined, meta, prepend, index, false);
+ return this._set(key, value, undefined, meta, undefined, false);
}
};
-/*
- If a given value was transformed and the journal was not initialized yet,
- create a journal and write the given value
-*/
LSD.Journal.prototype._finalize = function(key, value, old, meta, prepend, hash, val) {
if (val === value) return;
var journal = this._journal;
var group = journal && journal[key];
if (!group) (journal || (this._journal = {}))[key] = [val];
}
-/*
- LSD.Journal is a subclass of LSD.Object and thus it inherits a method
- named `change` that is an alias to `set` with predefined `old` argument.
- As a LSD.Object method it does nothing of interest, but in LSD.Journal
- it pops the value on top the stack and then adds a new value instead.
-
- The method is useful to alter the value by the key in journalized hash
- from the outside:
-
- object.set('a', 1); // adds value to stack
- console.log(object._journal.a) // [1]
- object.set('a', 2); // adds another value to the stack
- console.log(object._journal.a) // [1, 2]
- object.change('a', 3); // changes the value on top of the stack
- console.log(object._journal.a) // [1, 3]
-
- Change method removes a value on top from the journal, but that may lead to
- unexpected results, if the top value was set by another entity that does
- not expect that value to be removed. It is possible to avoid side-effects
- completely by unsetting specific value that is known to be given by the party
- that invokes `change`. It is easy to do within a callback, because callbacks
- in LSD receive both old and new value:
-
- object.watch('a', function(value, old, meta) {
- object.set('b', value, old, meta);
- })
-*/
LSD.Journal.prototype.change = function(key, value, old, meta, prepend) {
if (old === undefined) {
var group = this._journal;
@@ -258,7 +204,7 @@ LSD.Journal.prototype.change = function(key, value, old, meta, prepend) {
}
} else old = this[key];
}
- return this.set(key, value, old, meta, prepend);
+ return this._set(key, value, old, meta, prepend);
};
LSD.Struct.implement({
_nonenumerable: {
View
875 Source/Type/Object.js
@@ -18,48 +18,17 @@ provides:
...
*/
LSD.Object = function(object) {
- if (object != null) this._mix(undefined, object)
+ if (object != null)
+ this._mix(undefined, object)
};
LSD.Object.prototype.constructor = LSD.Object;
-/*
- Objects in LSD are different from regular objects in the way that LSD objects
- dont use or define getters at all. Values are precomputed and the good time
- to format or transform values is just before it was set. Object work with
- property observers and global listeners. Observing a property replaces some
- simple uses of Aspect Oriented Programming, an event system, and a pub/sub.
- It is possible because of the fact that all Object methods accept optional
- third parameter called `meta` that may describe what kind of operation led
- to the state change. It is easy to make dependent properties to
- know where the change come from, or what kind of change it is and react
- accordingly - e.g. avoid multiple or circular updates If a `meta` argument is
- not given, LSD automatically records cascade of changed properties and
- prevent circular property callbacks.
-*/
-LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash) {
-/*
- Objects may have a special `_hash` hook method that is invoked every time
- setter is called. It may do various things depending on the return value.
-
- * `undefined` value makes setter proceed as usual.
- * A string or number is used as a new key. In that case a hook may be used
- as a way to hash or transform keys.
- * `true` or `false` aborts setter, no callbacks are invoked. Useful for
- structs that need to extend or override the way setters work.
- * Other values make object invoke onChange callbacks
- without changing the state of an object. So callbacks or superclasses
- may implement custom storage logic. It is also possible to use an object
- as an immutable message dispatcher that way.
-
- A single object can have multiple hashing hooks. Each additional hashing
- function should be prefixed with underscore (like `__hash` and `___hash`).
- The hasher that is prefixed the most is called first.
-
-*/
+LSD.Object.prototype.set = function(key, value, old, meta, prepend, hash) {
+ //run hashing hooks to transform key
var stringy = typeof key == 'string';
- switch (hash) {
+ hasher: switch (hash) {
case undefined:
- hasher: for (var method = '_hash'; this[method]; method = '_' + method) {
- hash = this[method](key, value, old, meta, prepend, index);
+ for (var method = key == null ? '_join' : '_hash'; this[method]; method = '_' + method) {
+ hash = this[method](key, value, old, meta, prepend);
switch (typeof hash) {
case 'boolean':
return hash;
@@ -76,20 +45,168 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
break hasher;
}
};
- break;
+ break hasher;
case false:
hash = undefined;
}
-/*
- Object setters accept composite keys. LSD.Object constructs objects in the
- path (e.g. setting `post.title` will create a `post` object), and observes
- the whole path (post object may be changed and title property will be unset
- from the previous object and set to the new object)
-*/
- if (stringy && hash === undefined && typeof index != 'number')
- index = key.indexOf('.');
- if (index > -1)
- return this._mix(key, value, old, meta, null, null, null, index);
+ if (!meta && this._delegate) meta = this;
+ // merge objects
+ if (key == null) {
+ var unstorable = meta && meta._delegateble, val;
+ if (value) {
+ if (typeof value._watch == 'function') value._watch(undefined, {
+ fn: this._merger,
+ bind: this,
+ callback: this,
+ prepend: prepend
+ });
+ var skip = value._nonenumerable;
+ for (var prop in value)
+ if (value.hasOwnProperty(prop)
+ && (unstorable == null || !unstorable[prop])
+ && (skip == null || !skip[prop])
+ && (val = value[prop]) !== undefined) {
+ this._set(prop, val, undefined, meta, prepend);
+ }
+ };
+ if (old && typeof old == 'object') {
+ if (typeof old._unwatch == 'function')
+ old._unwatch(undefined, this);
+ var skip = old._nonenumerable;
+ for (var prop in old)
+ if (old.hasOwnProperty(prop)
+ && (unstorable == null || !unstorable[prop])
+ && (skip == null || !skip[prop])
+ && (val = old[prop]) !== undefined) {
+ this._set(prop, undefined, val, meta, prepend);
+ }
+ }
+ return true;
+ }
+ // set property in a foreign object, observe objects in path
+ if (!hash && stringy)
+ var index = key.indexOf('.', -1);
+ if (index > -1) {
+ var name = key.substr(key.lastIndexOf('.', index - 1) + 1, index) || '_owner';
+ var subkey = key.substring(index + 1);
+ if (this.onStore && this.onStore(name, value, old, meta, prepend, subkey) === false) return;
+ // store arguments to reuse when object in path changes
+ var storage = (this._stored || (this._stored = {}));
+ var group = (storage[name] || (storage[name] = []));
+ if (value !== undefined)
+ group.push([subkey, value, undefined, meta, prepend, hash]);
+ if (old !== undefined)
+ for (var i = 0, j = group.length; i < j; i++)
+ if (group[i][1] === old) {
+ group.splice(i, 1);
+ break;
+ }
+ var obj = this[name];
+ // build object in path
+ if (obj == null) {
+ if (value !== undefined && !this._nonenumerable[name] && !hash)
+ obj = this._construct(name, null, meta);
+ if (obj == null && this.onConstructRefused)
+ this.onConstructRefused(key, value, meta, old, prepend, hash)
+ // broadcast value to array
+ } else if (obj.push && obj._object !== true) {
+ var subindex = subkey.indexOf('.');
+ var prop = (subindex > -1) ? subkey.substring(0, subindex) : subkey;
+ if (parseInt(prop) == prop)
+ obj._mix(subkey, value, old, meta, prepend, hash)
+ else for (var i = 0, j = obj.length; i < j; i++)
+ obj[i]._mix(subkey, value, old, meta, prepend, hash);
+ // invoke a function
+ } else if (obj.apply) {
+ if (value !== undefined)
+ this[name](subkey, value);
+ if (old !== undefined) {
+ var negated = LSD.negated[name] || (LSD.negated[name] = LSD.negate(name));
+ this[negated](subkey, old)
+ }
+ // set property in object
+ } else {
+ if (obj._mix && obj._ownable !== false) {
+ if (!this._nonenumerable[name]
+ && value !== undefined
+ && old !== obj && this._owning !== false
+ && obj._shared !== true && obj._owner !== this)
+ obj = this._construct(name, null, 'copy', obj)
+ else
+ obj._set(subkey, value, old, meta, prepend, hash);
+ } else {
+ for (var previous, k, object = obj; (subindex = subkey.indexOf('.', previous)) > -1;) {
+ k = subkey.substring(previous || 0, subindex)
+ if (previous > -1 && object._mix) {
+ object._set(subkey.substring(subindex), value, old, meta, prepend, hash);
+ break;
+ } else if (object[k] != null)
+ object = object[k];
+ previous = subindex + 1;
+ }
+ k = subkey.substring(previous);
+ if (object._set)
+ object._set(k, value, old, meta, prepend, hash)
+ else
+ object[k] = value;
+ }
+ }
+ return true;
+ // merge objects by key
+ } else if (prepend === 'over' || prepend === 'under') {
+ var arg = value || old;
+ if (arg && typeof arg == 'object' && !arg.exec && !arg.push && !arg.nodeType) {
+ if (this.onStore && this.onStore(key, value, meta, old, prepend) === false) return;
+ var storage = (this._stored || (this._stored = {}));
+ var group = storage[key] || (storage[key] = []);
+ if (value !== undefined)
+ group.push([undefined, value, undefined, meta, prepend, hash, index]);
+ if (old !== undefined)
+ for (var i = 0, j = group.length; i < j; i++)
+ if (group[i][1] === old) {
+ group.splice(i, 1);
+ break;
+ }
+ var obj = this[key];
+ // set a reference to remote object without copying it
+ if (obj == null) {
+ if (value !== undefined && !this._nonenumerable[key])
+ obj = (value && value._mix && this._set(key, value, undefined, 'reference') && value)
+ || this._construct(key, null, meta);
+ // if remote object is array, merge object with every item in it
+ } else if (obj.push && obj._object !== true) {
+ for (var i = 0, j = obj.length; i < j; i++)
+ if (!meta || !meta._delegate || !meta._delegate(obj[i], key, value, old, meta))
+ obj[i]._mix(undefined, value, old, meta, prepend, hash);
+ } else if (obj._mix) {
+ var ref = obj._reference, owner = obj._owner;
+ // if there was an object referenced by that key, copy it
+ if (!this._nonenumerable[name]
+ && value !== undefined && obj !== old
+ && (!meta || !meta._delegate)
+ && !value._shared
+ && (ref && ref !== key || owner !== this)) {
+ obj = this._construct(key, null, 'copy', obj)
+ } else {
+ // swap objects
+ if (obj === old)
+ this._set(key, value, old, meta)
+ // merge objects
+ else if (obj !== value)
+ obj._mix(undefined, value, old, meta, prepend)
+ }
+ // merge into regular javascript object (possible side effects)
+ } else {
+ if (value !== undefined) for (var prop in value)
+ obj[prop] = value[prop];
+ if (old !== undefined) for (var prop in old)
+ if (old[prop] === obj[prop] && (value === undefined || old[prop] !== value[prop]))
+ delete old[prop];
+ }
+ return true;
+ }
+ }
+ // set value by key
var skip = this._nonenumerable;
var nonenum = skip[key];
var deleting = value === undefined
@@ -100,36 +217,24 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
return false;
if (deleting)
delete this[key];
- else this[key] = value;
+ else
+ this[key] = value;
}
-/*
- When objects link to other objects they write a link back to remote object.
- A linked object can access object that linked it with a private observable
- `_owner` property. Both linkee and linker objects may decide to avoid
- writing a link (e.g. Arrays dont write a link to its object values, and DOM
- elements dont let any objects write a link either).
-*/
if (nonenum !== true) {
- if (value != null && value._set && !value._owner && this._owning !== false)
- if (meta !== 'reference') {
+ if (value != null && value._set && this._owning !== false)
+ if (meta !== 'reference' && !value._owner) {
if (value._ownable !== false) {
value._reference = key;
value._set('_owner', this);
}
- } else value._references = (value._references || 0) + 1;
- if (old != null && old._owner === this)
+ }
+ if (old != null && old._owner === this && old._reference === key)
if (meta !== 'reference') {
old._set('_owner', undefined, this);
delete old._reference;
- } else old._references --;
+ }
}
-
-/*
- Most of the keys that start with `_` underscore do not trigger calls to
- global object listeners. But they can be watched individually. A list of
- the skipped properties is defined in `._nonenumerable` object in the end of a file.
- Builtin listeners may reject or transform value.
-*/
+ // run casting hooks to transform value
var changed, val = value;
for (var method = '_cast', i = 0; (changed === undefined || !nonenum) && this[method]; method = '_' + method)
if ((changed = this[method](key, value, old, meta, prepend, hash)) !== undefined)
@@ -140,24 +245,16 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
for (var method = '_finalize'; this[method]; method = '_' + method)
if (this[method](key, value, old, meta, prepend, hash, val) === true)
return true;
-/*
- Watchers are listeners that observe every property in an object. It may be
- a function (called on change) or another object (property change in
- original object will change in the watcher object)
-*/
+ // notify observers
var watchers = this._watchers;
if (watchers && nonenum !== true)
for (var i = 0, j = watchers.length, watcher, fn; i < j; i++) {
if ((watcher = watchers[i]) == null) continue;
this._callback(watcher, key, value, old, meta, prepend, hash, val);
}
-/*
- An alternative to listening for all properties, is to watch a specific
- property. Callback observers recieve key, new and old value on each property
- change.
-*/
if (stringy && !(index > -1)) {
- if (!deleting && hash === undefined && this[key] !== value) this[key] = value;
+ if (!deleting && hash === undefined && this[key] !== value)
+ this[key] = value;
var watched = this._watched;
if (watched && (watched = watched[key]))
for (var i = 0, fn; fn = watched[i++];)
@@ -165,14 +262,7 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
fn.call(this, value, old, meta, prepend, hash, val);
else
this._callback(fn, key, value, old, meta, prepend, hash, val);
-/*
- When an LSD.Object is mixed with a deep object, it builds missing objects
- to apply nested values. It also observes those objects for changes, so
- if any of them change it could re-apply the specific sub-tree of original nested
- object. Observing happens passively by storing links to sub-trees for each
- property that has nested object. When an object changes, it looks if it has
- any values stored for it to apply.
-*/
+ // apply stored arguments
var stored = this._stored, mem, k;
if (stored && (stored = stored[key]))
for (var i = 0, args; args = stored[i++];) {
@@ -180,7 +270,7 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
if (val === value) continue;
if (value != null && (!mem || !mem._delegate || !mem._delegate(value, key, val)))
if (value._mix)
- value._mix.apply(value, args);
+ value._set.apply(value, args);
else if (k == null) {
if (typeof value == 'object')
for (var p in val)
@@ -190,85 +280,19 @@ LSD.Object.prototype.set = function(key, value, old, meta, prepend, index, hash)
if (old != null && typeof old == 'object' && meta !== 'copy' && val !== old
&& (!mem || !mem._delegate || !mem._delegate(old, key, undefined, val, meta)))
if (old._unmix)
- old._unmix.apply(old, args);
+ old._unset.apply(old, args);
}
}
return true;
};
-/*
- Unset method cleans object key resetting its value to undefined.
-
- Unsetting is an important concept, that bears close resemblance to `delete`
- keyword in javascript. Using unset directly or indirectly throughout the
- code enables clean objects with predictable reusability patterns. Removing
- values and cleaning up side effects with `unset` and `unmix` methods goes a
- long way of programs without race conditions, that can be seemlessly
- assembled and disassembled at run time.
-
- LSD.Object#unset uses `delete` keyword to remove value from the object.
- It usually makes the key `undefined` in an object, if an object prototype
- does not have such key. If an object prototype has a value with the same
- key, the object will still reference that value after `delete` is called.
- LSD.Journal setters have similar behavior, it is possible to overwrite
- any value, but `unset`ting may revert the value to the one that was set
- previously. Repeated calls to `unset` will remove all values from the
- journal and the key will finally may become undefined.
-
- `unset` method does all things that `set` does in the same order: hashes its
- key, deals with ownership reference, transforms values, notifies
- observers, fires callbacks and processes stored arguments
-*/
LSD.Object.prototype.unset = function(key, value, old, meta, index, hash) {
return this._set(key, old, value, meta, index, hash);
};
-/*
- Get method fetches a value by a simple or composite keys. If an
- optional construct argument is given, it creates objects in place of
- missing properties.
-
- Despite the fact that older implementations of JavaScript do not allow
- native getter functions, `get` method in LSD is private and should not be
- used in places other than internals. The reason is that values in LSD are
- precomputed, and can be accessed like regular properties. There's no such
- thing as a getter method for a property in LSD.Object. If a property needs
- to be set in a right format, it gets transformed, constructed or parsed
- on set before it is assigned to the object.
-
- That also means that composite properties can not be described by a
- simple function that can be only executed when the property was requested.
- Properties that depend on more than one value may be implemented in two
- ways:
-
- * with LSD.Script expression in `imports` object that lazily creates
- individual observer objects for each variable used in parsed expression
- * or by listening to all properties and reacting to all involved
- properties and running a shared routine that tries to compose property
- from values it is observing.
-
- LSD.Script is fully magical, updates are lazy, happen in the right time and
- computed properties are always up to date. It's efficient in re-computation
- of expression, because it saves intermediate results for every branch of an
- expression syntax tree. So when values in expression change, it only
- recomputes affected parts of an expression. Still, LSD.Script has its own
- recursive evaluation model that has a large overhead and may be inappropriate
- in scenarios with many objects.
-
- LSD internals follow the second way of composing properties manually. It's
- the most memory efficient and LSD provides a few tools that may help in
- writing repetetive callbacks. Doing it manually may be very peformant, but
- needs tests that handle all possible situations.
- Another reason why the `get` method is so underused, is because LSD deals
- with observable objects and `get` function returns the value that object has
- at the time of getter invocation. If the value is changed, there's no way to
- know it for a variable that holds reference to a previous value. That is why
- `watch(key, callback) ` should be used instead, because it hides all the
- complexity of mutable state of objects without all the glue code
-*/
LSD.Object.prototype.get = function(key, construct, meta) {
for (var method = '_hash'; this[method] && hash === undefined; method = '_' + method) {
- var hash = this[method](key, undefined, undefined, meta);
+ var hash = this[method](key, undefined, undefined, meta, 'get');
switch (typeof hash) {
case 'boolean':
return;
@@ -296,412 +320,64 @@ LSD.Object.prototype.get = function(key, construct, meta) {
} else break;
}
};
-/*
- Mixing is a higher level abstraction above simply setting properties. `mix`
- method accepts both pairs of keys and values and whole objects to set and
- unset properties.
-
- Mixed values are stored twice in the object. Once, as the keys and values
- processed by setters, and another time is when original arguments are
- stored to be used later. For example, when an pair like
- `attributes.tabindex`: `1` is mixed into the object, the arguments are
- stored and then `tabindex` property is applied to `attributes` object. When
- `attributes` object changes, arguments are used to clean up the old object,
- and assign properties to the new object. Similar thing happens when deep
- nested objects are merged, it stores values on each level of the original
- object and can re-apply it to related struct objects when they change.
- When an observable object is mixed, it can be opted-in for "live" merging,
- when updates to the merged object will propagate into the object it was
- merged into. By default, all new and updated values are appended on top,
- overwriting values that were set previously. When `prepend` argument is
- given, reverse merging will be used instead, applying values to the bottom
- of the stack. That will make merged object never overwrite the values that
- were there before. Those will only be used when the values that shadows the
- merged values will be unset.
-*/
-LSD.Object.prototype.mix = function(key, value, old, meta, merge, prepend, lazy, index) {
- if (!meta && this._delegate) meta = this;
-/*
- // mix an object
- this.mix(undefined, object)
- // mix object, unmix old object
- this.mix(undefined, object, old)
-
- // unmix method is an alias to mix that passes value as old value
- this.unmix(undefined, object);
- // is the same as:
- this.mix(undefined, undefined, object)
-
- // mix & observe objects
- this.mix(undefined, object, old, meta, true)
- // reverse merge, does not overwrite present keys
- this.mix(undefined, object, old, meta, true, true)
-*/
- if (key == null) {
- var unstorable = meta && meta._delegateble, val;
- if (value) {
- if (value._watch) value._watch({
- fn: this._merger,
- bind: this,
- callback: this,
- prepend: prepend
- });
- var skip = value._nonenumerable;
- for (var prop in value)
- if (value.hasOwnProperty(prop) && (unstorable == null || !unstorable[prop]) && (skip == null || !skip[prop]))
- if ((val = value[prop]) != null && val._ownable === false)
- this._set(prop, val, undefined, meta, prepend);
- else
- this._mix(prop, val, undefined, meta, merge, prepend, lazy);
- };
- if (old && typeof old == 'object') {
- if (old._unwatch)
- old._unwatch(this);
- var skip = old._nonenumerable;
- for (var prop in old)
- if (old.hasOwnProperty(prop) && (unstorable == null || !unstorable[prop]) && (skip == null || !skip[prop]))
- if ((val = old[prop]) != null && val._ownable === false)
- this._set(prop, undefined, val, meta, prepend);
- else
- this._mix(prop, undefined, val, meta, merge, prepend, lazy);
- }
- return this;
- }
-/*
- A string in the key may contain dots `.` that denote nested objects. The
- values are passed through to the related objects, but they are also stored
- in original object, so whenever related object reference is changed, the
- stored values are removed from old objects and applied to the new related
- object.
-
- // create & observe object, set value by key
- this.mix('object.key', true);
- // when objects in path change, a value migrates to the new object
- var object = new LSD.Object;
- this.set('object', object);
- object.key // true
-*/
- if (index == null)
- index = key.indexOf('.', -1);
- if (index > -1) {
- var name = key.substr(key.lastIndexOf('.', index - 1) + 1, index) || '_owner';
- var subkey = key.substring(index + 1);
- if (this.onStore && this.onStore(name, value, old, meta, prepend, subkey) === false) return;
- var storage = (this._stored || (this._stored = {}));
- var group = (storage[name] || (storage[name] = []));
- if (value !== undefined)
- group.push([subkey, value, undefined, meta, merge, prepend, lazy]);
- if (old !== undefined)
- for (var i = 0, j = group.length; i < j; i++)
- if (group[i][1] === old) {
- group.splice(i, 1);
- break;
- }
- var obj = this[name];
- if (obj == null) {
- if (value !== undefined && !this._nonenumerable[name] && !lazy)
- obj = this._construct(name, null, meta);
- if (obj == null && this.onConstructRefused)
- this.onConstructRefused(key, value, meta, old, merge, prepend, lazy)
- } else if (obj.push && obj._object !== true) {
- var subindex = subkey.indexOf('.');
- var prop = (subindex > -1) ? subkey.substring(0, subindex) : subkey;
- if (parseInt(prop) == prop)
- obj._mix(subkey, value, old, meta, merge, prepend, lazy)
- else for (var i = 0, j = obj.length; i < j; i++)
- obj[i]._mix(subkey, value, old, meta, merge, prepend, lazy);
- } else if (obj.apply) {
- if (value !== undefined)
- this[name](subkey, value);
- if (old !== undefined) {
- var negated = LSD.negated[name] || (LSD.negated[name] = LSD.negate(name));
- this[negated](subkey, old)
- }
- } else {
-/*
- When objects are merged together, nested objects are opted in for a
- copy-on-write merging, that speeds up merging by directly referencing
- objects that are not changed in the process merging.
-
- A controlled side effect e.g. `mix('object.key', true)` will safely
- modify an `object` if it was referenced in a copy-on-write merge by
- creating a new object that is subscribed to all values and changes of a
- referenced object.
-*/
- if (value !== undefined && old !== obj && this._owning !== false && obj._shared !== true
- && (obj._owner ? obj._owner !== this : obj._references > 0)) {
- obj = this._construct(name, null, 'copy', obj)
- } else if (obj._mix && obj._ownable !== false) {
- obj._mix(subkey, value, old, meta, merge, prepend, lazy);
- } else {
-/*
- A composite key given to a `mix` method affects an outside object be it
- observable or not. `mix('element.parentNode.id', 123)` will walk through
- references and set the id of a real DOM element. It will observe `element`
- reference for changes, but not `parentNode`, since DOM elements dont
- support observable API, unlike LSD.Elements. Although when `element`
- changes, the id will be removed from a `parentNode` of an old element if
- there were no unobserved changes to `parentNode` reference.
-*/
- for (var previous, k, object = obj; (subindex = subkey.indexOf('.', previous)) > -1;) {
- k = subkey.substring(previous || 0, subindex)
- if (previous > -1 && object._mix) {
- object._mix(subkey.substring(subindex), value, old, meta, merge, prepend, lazy);
- break;
- } else if (object[k] != null)
- object = object[k];
- previous = subindex + 1;
- }
- k = subkey.substring(previous);
- if (object._mix)
- object._mix(k, value, old, meta, merge, prepend, lazy)
- else
- object[k] = value;
- }
- }
-/*
- // set a value by key
- this.mix('key', 'value')
- // mix in object by the key (copy)
- this.mix('key', object)
- // mix & unmix objects by the key
- this.mix('key', object, old, meta)
-
- // merge observable object by the key with copy-on-write reference
- this.mix('key', object, old, meta, true) // this.key === object
- // direct mutation changes referenced object
- this.key.mix('sing', true) // this.key === object
- // controlled mutation creates an observed copy of ref'd object
- this.mix('key.dance', true) // this.key !== object
-*/
- } else if ((value === undefined && old != null && typeof old == 'object' && !old[this._trigger] && !old._ignore)
- || (value != null && typeof value == 'object' && !value.exec && !value.push
- && !value.nodeType && !value[this._trigger] && (!value._mix || merge))) {
- if (this.onStore && this.onStore(key, value, meta, old, prepend) === false) return;
- var storage = (this._stored || (this._stored = {}));
- var group = storage[key] || (storage[key] = []);
- if (value !== undefined)
- group.push([undefined, value, undefined, meta, merge, prepend, lazy, index]);
- if (old !== undefined)
- for (var i = 0, j = group.length; i < j; i++)
- if (group[i][1] === old) {
- group.splice(i, 1);
- break;
- }
-/*
- When a deep object is mixed into an object, it construct objects on its
- path to set the values. The base class for those objects is determined
- dynamically, if `getConstructor` method is defined, or resorts to
- `this.constructor` which creates the same kind of object. When an object
- is mixed with merge mode turned on, and there was no object by the same
- key already, mix will attempt to reference a merged object marking it for
- a copy-on-write. It means that merged object will be the same object as
- was given in arguments, but there will be a copy made on any attempt to
- modify the object (e.g. by merging another object by the same key).
-*/
- var obj = this[key];
- if (obj == null) {
- if (value !== undefined && !this._nonenumerable[key])
- obj = (merge && value && value._mix && this._set(key, value, undefined, 'reference') && value)
- || this._construct(key, null, meta);
-/*
- Objects also support mixing values into arrays. They mix values
- into each value of the array.
-
- // set disabled property to every object in childNodes array
- this.mix('childNodes.disabled', true)
-*/
- } else if (obj.push && obj._object !== true) {
- for (var i = 0, j = obj.length; i < j; i++)
- if (!meta || !meta._delegate || !meta._delegate(obj[i], key, value, old, meta))
- obj[i]._mix(undefined, value, old, meta, merge, prepend, lazy);
- } else if (obj._mix) {
-/*
- When object is merged into another object by some key, it will be first
- referenced directly avoiding a full blown copy of an object. If an outside
- object will attempt a controlled mutation of a referenced object, the copy
- will be created to replace a reference. The copy will hold both values of
- a referenced object and a side effect from the mutation.
-*/
- if (meta === 'copy') {
- this._set(key, value, old, meta, prepend)
- } else {
- var ref = obj._reference, owner = obj._owner;
- if (value !== undefined && obj !== old && (!meta || !meta._delegate) && (ref && ref !== key
- || (owner ? owner !== this : obj._references > 0) && obj._shared !== true))
- obj = this._construct(key, null, 'copy', obj)
- else {
- if (obj === old)
- this._set(key, value, old, meta, prepend)
- else if (obj !== value)
- obj._mix(undefined, value, old, meta, merge, prepend)
- }
- }
- } else {
- if (value !== undefined) for (var prop in value)
- obj[prop] = value[prop];
- if (old !== undefined) for (var prop in old)
- if (old[prop] === obj[prop] && (value === undefined || old[prop] !== value[prop]))
- delete old[prop];
- }
- } else {
- this._set(key, value, old, meta, prepend);
- }
- return this;
+LSD.Object.prototype.mix = function(key, value, old, meta, prepend, lazy) {
+ return this._set(key, value, old, meta, prepend || 'over', lazy)
};
-/*
- Unlike most of the hash table and object implementations out there,
- LSD.Object can easily unmix values from the object. The full potential of
- unmixing objects can be explored with LSD.Journal, that allows objects to be
- mixed on top or to the bottom of the stack (some kind of reverse merge known
- in ruby).
-
- Plain LSD.Object is pretty naive about unmixing properties, it just tries to
- unset the ones that match the given values. LSD.Journal on the other hand, is
- pretty strict and never loses a value that was set before, and can easily
- reset to other values that were set before by the same key.
-
- Different kind of objects often used nested together, so `mix` being a
- recursive function often helps to pass the commands through a number of
- objects of different kinds.
- `unmix` method has the same argument signature as `mix` function, although
- it ignores `old` argument and uses `value` instead when calling `mix`. It
- comes in handy when using stored arguments that may be processed with either
- state without destructuring or accessing each of function's 8 arguments by
- index.
-*/
-LSD.Object.prototype.unmix = function(key, value, old, meta, merge, prepend, lazy, index) {
- return this._mix(key, old, value, meta, merge, prepend, lazy, index)
+LSD.Object.prototype.unmix = function(key, value, old, meta, prepend, lazy) {
+ return this._set(key, old, value, meta, prepend || 'over', lazy)
};
-/*
- Merge method is an alias to mix with some arguments predefined. It does
- the same as simple mix, but it also tries to subscribe current object to
- changes in the object that is being mixed in, if it's observable. Changes
- then propagate back to current object. `prepend` argument defines if the
- object should be merged to the bottom or on top.
-*/
+
LSD.Object.prototype.merge = function(value, prepend, meta, old) {
- return this._mix(undefined, value, old, meta, true, prepend)
+ return this._set(undefined, value, old, meta, prepend || 'over')
};
-/*
- Unmerge method unmixes the object and unsubscribes current object from
- changes in the given object.
-*/
+
LSD.Object.prototype.unmerge = function(value, prepend, meta) {
- return this._mix(undefined, undefined, value, meta, true, prepend)
+ return this._set(undefined, undefined, value, meta, prepend || 'over')
};
-/*
- Observing is all about passing around the callback and executing it at the
- right time. What makes observing objects practical is the fact that callbacks
- are called with a new value, the previous value that was overriten and
- optional data argument describing the mutation. There may not be a reference
- to the old value in the object anymore, but it still exists in a call chain
- of synchronous callbacks arguments. `_callback` method also solves a problem
- of circular callbacks that may possibly hang execution. The callback record
- and a reference to a previous value is passed to all callbacks, but not
- stored or referenced in objects.
- Another use case for referencing the previous values is that it allows to
- aggregate data from different sources with overlapping keys. The bad way to
- avoid callbacks be fired multiple times is to surpress execution of callbacks
- until it is known that all properties have right value it's safe to proceed.
- That approach is used in many frameworks that are based on event loops, but
- it gives too much control to developer and results in confusion and bugs. It
- also makes a developer write glue code, and require him to decide **when**
- it's time to do a batch of changes but in asynchronous code you may never
- know. The better approach is to buffer up values in a dedicated object (e.g.
- many CSS rules may define a font size of a specific element. But in the end
- only one declaration is used. An object may hold references to all the
- values, but decide which to use). When such dedicated object is observed, it
- fires callbacks when the keys change and it always send reference to previous
- value allowing a developer to choose the optimal way of transitioning from
- one value to another in his callback. Will be it be full redraw of a block,
- notification of child nodes, or a successful cache lookup. It enables "black
- box" abstractions where objects simply export some properties, but the
- behavior that makes one properties result in changing other is completely
- hidden from an uninterested spectator. Everything to separate state from
- logic.
-
- LSD Objects support two ways of observing values:
-*/
LSD.Object.prototype.watch = function(key, value, old, meta, lazy) {
-/*
- * A single argument without a pair is treated like a **Global observer** that
- recieve changes to all properties in an object. It supports different
- kind of values:
-
- * function - a de-facto standart for values, the most flexible one,
- for high-order data flows.
- * Bound object, a simple object with `bind` object and `method`
- property or a `fn` function. Objects are often used internally, and they
- are made to link to an external function to avoid creating a needless
- closure that a functional value implies.
- * Another observable object - the most efficient way, since it stores
- only a reference to another object. It results in "linking" objects
- together, so changes in observed object will be propagated to argument
-*/
- var string = typeof key == 'string';
- var single = value == null && !string && (key || old !== undefined)
- if (!single)
- for (var method = '_hash'; this[method]; method = '_' + method) {
- var hash = this[method](key, value, old, meta, lazy);
- switch (typeof hash) {
- case 'string':
- key = hash;
- string = true;
- break;
- case 'object':
- if (value) {
- if (typeof hash.push == 'function')
- hash.push(value)
- else
- hash.watch(key, value);
- value = undefined;
- }
- if (old) {
- if (typeof hash.splice == 'function')
- for (var i = hash.length, fn; i--;) {
- if ((fn = hash[i]) == old || fn.callback == old) {
- hash.splice(i, 1);
- break;
- }
+ for (var method = key == null ? '_join' : '_hash'; this[method]; method = '_' + method) {
+ var hash = this[method](key, value, old, meta, 'watch');
+ switch (typeof hash) {
+ case 'string':
+ key = hash;
+ string = true;
+ break;
+ case 'object':
+ if (value) {
+ if (typeof hash.push == 'function')
+ hash.push(value)
+ else
+ hash.watch(key, value);
+ value = undefined;
+ }
+ if (old) {
+ if (typeof hash.splice == 'function')
+ for (var i = hash.length, fn; i--;) {
+ if ((fn = hash[i]) == old || fn.callback == old) {
+ hash.splice(i, 1);
+ break;
}
- else
- hash.unwatch(key, old);
- old = undefined;
- }
- }
+ }
+ else
+ hash.unwatch(key, old);
+ old = undefined;
+ }
}
- if (single) {
+ }
+ if (!key) {
var watchers = (this._watchers || (this._watchers = []));
- if (key !== undefined)
- watchers.push(key)
+ if (value !== undefined)
+ watchers.push(value)
if (old !== undefined) for (var i = 0, j = watchers.length, fn; i < j; i++) {
var fn = watchers[i];
- if (fn === key || (fn != null && fn.callback == key))
+ if (fn === old || (fn != null && fn.callback == old))
watchers.splice(i, 1);
break;
}
-/*
- * A pair of key and value allow observing of an **individual property**.
- Unlike global observers, that only listens for public properties, it is
- possible to listen for a private property like `_owner` that links to
- another object that holds the reference to this object. Some objects opt
- out of writing or claiming `_owner` reference, but it is there by default
- and used for observing external related objects.
-*/
} else {
-/*
- The upside of having a consistent observable environment is that it is
- possible to seemlessly stack observations together. A complex key with dot
- delimeters may be given to a `watch` function and it will start observing
- separate keys. If an object changes somewhere along the path, overriden
- object gets cleaned from observations, and the new object become observed.
- It works with keys of any deepness, and lazy execution ensures that there
- isn't too much junk around.
-*/
var index = key.indexOf('.');
if (index > -1) {
this._watch(key.substr(0, index) || '_owner', value && {
@@ -713,15 +389,6 @@ LSD.Object.prototype.watch = function(key, value, old, meta, lazy) {
lazy: lazy
}, typeof old == 'string' ? [this, old] : old, meta, lazy)
} else {
- if (lazy && typeof value == 'string') {
- value = {
- fn: this._watcher,
- key: value,
- callback: value,
- meta: meta,
- lazy: lazy
- }
- }
var val = this._get(key, value && lazy === false);
var watched = (this._watched || (this._watched = {}));
watched = (watched[key] || (watched[key] = []))
@@ -756,40 +423,11 @@ LSD.Object.prototype.watch = function(key, value, old, meta, lazy) {
}
}
};
-/*
- Unwatch is a mirror of a `watch` method that does the opposite, removes an
- observer and fires it if there was an actual observed value. Most of APIs
- that can add and remove callbacks suffer from the same plague - it needs a
- reference to exactly the same callback given to remover method to find it and
- remove it from a list. `unwatch` is a little more helpful, as it allows a
- callback to have an optional `callback` property that may be used to match
- the callback. It may just as well be unique id, rather than a reference to an
- object, but an id has to be stored somewhere, and a reference is often
- already there.
-*/
+
LSD.Object.prototype.unwatch = function(key, value, old, meta, lazy) {
return this._watch(key, old, value, meta, lazy);
};
-/*
- In some situations object needs to construct another object and assign it
- by a specific key. It may happen when another object with nested objects
- is merged in, so to make a deep copy of it, the original object needs to
- construct its own copies of all objects to hold nested values. Setting a
- nested key like `foo.bar` may also result in building a `foo` object to
- hold the `bar` key with given value.
-
- A private `_construct` method is a dynamic way to figure out the right
- constructor for an object to build. It tries to call a `_getConstructor`
- method first and use a returned value as a constructor. Then it tries to
- use a constructor of a given value, or falls back to use the same
- constructor as the object itself has.
-
- `_onBeforeConstruct` hook may provide its own instantiation strategy.
- Object may have a `_constructors` object, possibly shared with other
- objects via prototype, that holds the cache of resolved constructors for
- each key. It may also be used as a dictionary of constructors without help
- of `_getConstructor` hook
-*/
+
LSD.Object.prototype._construct = function(name, constructor, meta, value) {
var constructors = this._constructors;
if (constructors)
@@ -812,15 +450,7 @@ LSD.Object.prototype._construct = function(name, constructor, meta, value) {
}
return instance;
};
-/*
- Observing a chain of properties in some frameworks result in
- callback-hell, when the functions are nested very deeply and it's
- impossible to decouple the program flow to something more reasonable. LSD
- instead provides a public interface to observe deeply properties and not
- bother about saving the state of execution somewhere. Instead of creating
- closures, for each observed property, lsd references a single function
- internally.
-*/
+
LSD.Object.prototype._watcher = function(call, key, value, old, meta) {
for (var i = 0, object; i < 2; i++) {
if ((object = (i ? value : old)) == null) continue;
@@ -840,16 +470,11 @@ LSD.Object.prototype._watcher = function(call, key, value, old, meta) {
}
}
};
+
LSD.Object.prototype._merger = function(call, name, value, old, meta) {
- this._mix(name, value, old, meta, true, call.prepend);
+ this._set(name, value, old, meta, call.prepend);
};
-/*
- All LSD functions that accept callbacks support a various number of callback
- types by using a shared `_callback` method everywhere to figure out how to
- dispatch the given callback. It adds to consistensy and API richness across
- all observable structures. It also uses optional `meta` argument to record
- a trace of all properties affected by callbacks to avoid curcular calls
-*/
+
LSD.Object.prototype._callback = function(callback, key, value, old, meta, lazy) {
switch (typeof callback) {
case 'function':
@@ -874,28 +499,24 @@ LSD.Object.prototype._callback = function(callback, key, value, old, meta, lazy)
if (a[0] == this && a[1] == property) return;
meta.push([this, key]);
}
- if (typeof lazy == 'number') {
+ if (typeof lazy == 'number') {
var prop = subject._properties;
if (!prop || !(prop = prop[property]) || !prop.chunked)
return;
}
- subject._mix(property, value, old, meta, true, lazy);
+ subject._set(property, value, old, meta, lazy);
};
-/*
- LSD.Object internals share the same name space of an object with its
- public properties. When an object is iterated it often does not expect
- internal keys be mixed with the data, so an iterator needs to skip keys.
- Most of the internals dont use this method internally, because they have
- it inlined, but it's the best way to tell private property from public.
-*/
+
LSD.Object.prototype.has = function(key) {
return this.hasOwnProperty(key) && !this._nonenumerable[key];
};
+
LSD.Object.prototype.forEach = function(callback, bind) {
for (var property in this)
if (this.hasOwnProperty(property) && !this._nonenumerable[property])
callback.call(bind || this, this[property], property);
};
+
LSD.Object.prototype.getKeys = function() {
var keys = [];
for (var property in this)
@@ -903,15 +524,9 @@ LSD.Object.prototype.getKeys = function() {
keys.push(property);
return keys;
};
+
LSD.Object.prototype.change = LSD.Object.prototype.set;
-/*
- A function that recursively cleans LSD.Objects and returns plain object
- copy of the values. It is used on rare occasions where there needs to be a
- clean snapshot of an object expored to some foreign function (e.g. when
- passing observable data to some native JS API). It works, but it should
- not be used when it's possible.
-*/
LSD.Object.prototype.toObject = function(normalize, serializer) {
if (this === LSD.Object || this === LSD)
var obj = normalize, normalize = serializer, serializer = arguments[2];
@@ -973,39 +588,23 @@ LSD.Object.prototype.toObject = function(normalize, serializer) {
};
LSD.toObject = LSD.Object.toObject = LSD.Object.prototype.toObject;
LSD.Object.prototype._trigger = '_calculated';
-/*
- Dictionary is the same as observable objects, but it only
- has underscored API methods to avoid key occlusion.
-*/
LSD.Dictionary = function(object) {
if (object != null) this._mix(undefined, object)
};
-/*
- A dictionary of internal keys that get skipped when iterating over all keys
- of object. Some of these properties are skipped naturally by using a
- hasOwnProperty function that filters out all properties that come from the
- prototype of the object. But with this skip map, properties may be assigned
- to the object itself and still be skipped by iterators.
-*/
+
LSD.Object.prototype._nonenumerable = {
_nonenumerable: true,
- _references: true,
_reference: true,
- __finalize: true,
- _finalize: true,
+ __finalize: true, _finalize: true,
_watchers: true,
_scripts: true,
_watched: true,
_ownable: true,
_owning: true,
_stored: true,
- ___hash: true,
- ___cast: true,
- _owner: true,
- __hash: true,
- __cast: true,
- _cast: true,
- _hash: true
+ ___hash: true, __hash: true, _hash: true,
+ ___cast: true, __cast: true, _cast: true,
+ _owner: true
};
!function(proto, dict) {
dict._nonenumerable = {};
View
6 Source/Type/Storage.js
@@ -27,7 +27,7 @@ provides:
* `LSD.Storage(key)` to unset a value by key
* `LSD.Storage(array)` to sync array with storage (store values from array
and fetch values from cookies, use array values in case of conflicts)
- * `array.watch(LSD.Storage.Cookies)` to use it as a callback (won't retrieve values
+ * `array.watch(undefined, LSD.Storage.Cookies)` to use it as a callback (won't retrieve values
from storage initially, but will save changes in array)
* `[1,2,3].forEach(LSD.Storage)` to store values from native array.
* `LSD.Storage([])` to populate a native array with values from cookies
@@ -115,7 +115,7 @@ LSD.Storage = function(key, value, old, meta, prefix, storage, get, self, length
return (result == null) ? undefined : result;
} else {
if (context.push && context._watch)
- context.watch(self);
+ context.watch(undefined, self);
storage.getAllItems(prefix, callback || context || this, meta, storage);
}
};
@@ -195,7 +195,7 @@ LSD.Storage.Cookies.prototype.getItem = function(key, prefix, callback, meta) {
var eql = cookie.indexOf('=', p);
var index = cookie.substring(p + skip, eql);
if (key && index != key) continue;
- var val = cookie.substring(eql + 1, i);
+ var val = cookie.substring(eql + 1, i > -1 ? i : undefined);
if (callback) this.callback(callback, index, val, meta);
if (key) return val;
}
View
134 Source/Type/Struct.js
@@ -37,11 +37,6 @@ LSD.Struct = function(properties, Structs, Sub) {
if (!(this instanceof Struct))
return new Struct(arguments[0], arguments[1])
Struct.instances.push(this);
-/*
- Inherited properties is an internal concept that allows an instance of a
- class to recieve its own copy of a private object from prototype without
- recursive cloning.
-*/
for (var i = 0, obj = this._unlinked, inherited, group, cloned, value; obj && (inherited = obj[i++]);)
if ((group = this[inherited])) {
var cloned = this[inherited] = {};
@@ -50,10 +45,6 @@ LSD.Struct = function(properties, Structs, Sub) {
cloned[property] = typeof value.push == 'function' ? value.slice() : value
}
}
-/*
- A Struct may have a _preconstructed array defined in a prototype that lists
- all nested property names that should be initialized right away.
-*/
var preconstructed = this._preconstructed;
if (preconstructed) for (var i = 0, type, constructors = this._constructors; type = preconstructed[i++];) {
var constructor = constructors[type] || (constructors[type] = this._getConstructor(type));
@@ -72,8 +63,8 @@ LSD.Struct = function(properties, Structs, Sub) {
if (scripted) for (var i = 0, property; property = scripted[i++];)
this._watch(this._properties[property].script, property);
var subject = this._initialize && this._initialize.apply(this, arguments) || this;
- if (typeof object == 'object')
- subject.mix(undefined, object);
+ if (typeof object == 'object')
+ subject._mix(undefined, object);
return subject
}
if (!Structs) Structs = [];
@@ -115,9 +106,13 @@ LSD.Struct = function(properties, Structs, Sub) {
}
Struct.prototype._properties = Struct.properties = props || properties || {};
if (properties) {
+ var nonenum = properties._nonenumerable;
for (var name in properties) {
+ if (!properties.hasOwnProperty(name) || (nonenum && nonenum[name]))
+ continue;
var val = properties[name];
- if (val === undefined) continue;
+ if (val === undefined)
+ continue;
var mutator = LSD.Struct.Mutators[name];
if (mutator)
mutator.call(Struct, val);
@@ -126,49 +121,34 @@ LSD.Struct = function(properties, Structs, Sub) {
}
}
return Struct;
-};
-LSD.Struct.implement = function(object, host, reverse) {
+};
+LSD.Struct.implement = function(object, host, reverse, plain) {
if (!host) host = this.prototype;
for (var prop in object) {
var value = object[prop];
- if (typeof value == 'object' && value != null && !value.push && !value.exec && prop !== '_owner') {
+ if (!plain && typeof value == 'object' && value != null && !value.push && !value.exec && prop !== '_owner') {
var hosted = host[prop];
if (hosted) {
if (!host.hasOwnProperty(prop))
hosted = host[prop] = LSD.Struct.implement({}, hosted);
} else hosted = host[prop] = {}
LSD.Struct.implement(value, hosted)
- } else if (!reverse || !host[prop] || prop == 'toString') host[prop] = value;
+ } else if (!reverse || !host[prop] || prop == 'toString')
+ host[prop] = value;
}
return host;
}
LSD.Struct.prototype.implement = LSD.Struct.implement;
-/*
- Every property defined in a class properties object will be treated like a
- property, unless it is defined in the Mutators object. Mutators are a hooks
- that allow some DSL functions to be added to handle some sugary class
- definitions.
-*/
LSD.Struct.Mutators = {
-/*
- Structs are slightly compatible with mootools classes, and can use Extends
- property to inherit the prototype from given object.
-*/
Extends: function(Klass) {
if (Klass.push) return LSD.Struct.Mutators.Implements.call(this, Klass);
this.implement(Klass.prototype, null, true);
},
-/*
- Implements directive mixes in multiple object prototypes in order.
-*/
Implements: function(Klasses) {
if (!Klasses.push) return LSD.Struct.Mutators.Extends.call(this, Klasses);
for (var i = 0, j = Klasses.length; i < j; i++)
this.implement(Klasses[i].prototype, null, true);
},
-/*
-
-*/
Formulas: function(Formulas) {
var prototype = this.prototype;
var formulas = (prototype.formulas || (prototype.formulas = {}))
@@ -188,34 +168,24 @@ LSD.Struct.Mutators = {
}
};
LSD.Struct.prototype._hash = function(key, value, old, meta, extra) {
- /*
- Objects accept special kind of values, compiled LSD.Script expressions.
- They may use other keys in the object as observable variables, call
- functions, fetch and iterate data. Script updates value for the key when it
- computes its value asynchronously. The value stays undefined, while the
- script doesn't have enough data to compute.
- */
- if (arguments.length < 5) return;
+ if (extra == 'get' || (!key && extra == 'watch')) return;
var stringy = typeof key == 'string';
var trigger = this._trigger;
var vscript = value && value[trigger] && !value._ignore;
var oscript = old && old[trigger] && !old._ignore;
if (stringy) {
- var regex = this._watchable;
- if (!regex) return;
if (key.charAt(0) == '^')
key = '.' + key.substring(1);
- if (key.match(regex)) {
- if (!vscript && !oscript)
- return;
- } else
- key = LSD.Script(key)
+ if (!this._composite && !/^[a-z0-9-_.]*$/i.test(key))
+ key = (this._parse || LSD.Script)(key);
+ else if (!vscript && !oscript)
+ return;
}
if (key[trigger]) {
if (value)
- this._hash(value, key, undefined, meta, extra)
+ this._hash(value, key, undefined, meta, 'watch')
if (old)
- this._hash(old, undefined, key, meta, extra)
+ this._hash(old, undefined, key, meta, 'watch')
return true;
}
if ((vscript || (value = undefined)) == (oscript || (old = undefined)))
@@ -242,6 +212,8 @@ LSD.Struct.prototype._hash = function(key, value, old, meta, extra) {
}
}
} else {
+ var literal = this._literal;
+ if (literal && literal[key]) return;
if (old) {
old.set('attached', undefined, old.attached, meta);
old.set('value', undefined, old.value, meta)
@@ -250,7 +222,7 @@ LSD.Struct.prototype._hash = function(key, value, old, meta, extra) {
if (!value && scripts)
delete scripts[key]
}
- if (value)
+ if (value)
if (node) {
scripts[key] = LSD.Script(value, null, [this, key]);
node._watch('variables', '_scripts.' + key + '.scope');
@@ -291,11 +263,11 @@ LSD.Struct.prototype._cast = function(key, value, old, meta, extra) {
fn: this['_' + fn],
bind: this
};
- this.mix(alias.substring(0, index) + '.' + value, this[fn], undefined, meta);
+ this._set(alias.substring(0, index) + '.' + value, this[fn], undefined, meta);
if (old != null && typeof old != 'object')
- this.mix(alias.substring(0, index) + '.' + old, undefined, this[fn], meta);
+ this._set(alias.substring(0, index) + '.' + old, undefined, this[fn], meta);
} else
- this.mix(alias, value, old, meta);
+ this._set(alias, value, old, meta);
};
} else if (this._aggregate && !this._nonenumerable[key]) {
var storage = (this._stored || (this._stored = {}))
@@ -331,22 +303,15 @@ LSD.Struct.prototype._cast = function(key, value, old, meta, extra) {
return value;
};
LSD.Struct.prototype._finalize = function(key, value, old, meta, extra, hash) {
-
- /*
- Global object listeners (and so custom property handlers in structs)
- may compile given value into expression (e.g. a textnode may find
- interpolations in `textContent` property).
- */
- if (value != null && value[this._trigger] != null && !value._ignore) {
+ if (!this._nonenumerable[key] && value != null
+ && value[this._trigger] != null && !value._ignore) {
+ var literal = this._literal;
+ if (literal && literal[key]) return;
if (hash === undefined) this[key] = old;
this._watch(value, key, undefined, meta);
return true;
}
}
-/*
- - An LSD.Object constructor, used to instantiate nested objects with specific
- class
-*/
LSD.Struct.prototype._getConstructor = function(key) {
var prop = this._properties[key];
if (prop)
@@ -382,38 +347,6 @@ LSD.Struct.prototype._onBeforeConstruct = function(key) {
}
}
};
-/*
- There's a way to define dynamic properties and two-way references by using
- `exports` & `imports` object directives. Properties defined in those objects
- will be defined when object is instantiated, unlike property dictionaries
- that are lazy in the way that when object is created, it does not iterate the
- dictionary and only looks it up when actual properties change.
-
- var Struct = new LSD.Struct({
- total: {
- script: 'sum * rate * (1 - tax)'
- }
- });
- Struct.prototype.rate = 1;
- Struct.prototype.tax = 0.15
- var struct = new Struct;
- expect(struct.total).toBeUndefined();
- struct.set('sum', 200);
- expect(struct.total).toBe(200 * 0.85);
- struct.set('sum', 85);
- expect(struct.total).toBe(85 * 0.85);
- struct.set('tax', 0.2);
- expect(struct.total).toBe(85 * 0.8);
-
- LSD.Script is compiled when struct is instantiated. It starts observing
- variables in expression from left to right. In example above it starts
- by observing `sum`, and only when it gets it, it starts observing `rate`
- and after that `tax` variables. When all variables are found, the result
- is calculated and assigned to a property named `total`.
-*/
-LSD.Struct.prototype._linker = function(call, key, value, old, meta) {
- this.mix(call.key, value, old, meta);
-};
LSD.Struct.prototype._unlinked = ['_journal', '_stored'];
LSD.Struct.prototype._watchable = /^[a-zA-Z0-9._-]+?$/;
LSD.Struct.prototype._nonenumerable = {
@@ -424,8 +357,7 @@ LSD.Struct.prototype._nonenumerable = {
_constructors: true,
_Properties: true,
_properties: true,
- _scripted: true,
- _linker: true
+ _scripted: true
}
!function(proto) {
for (var property in proto)
@@ -515,8 +447,10 @@ LSD.Struct.Property.prototype = new (LSD.Struct({
var instances = this._owner._struct;
var reference = this._reference;
for (var i = 0, instance; instance = instances[i++];) {
- if (value) instance._watch(value, reference);
- if (old) instance._watch(old, undefined, reference);
+ if (value)
+ instance._watch(value, reference);
+ if (old)
+ instance._watch(old, undefined, reference);
}
},
Please sign in to comment.
Something went wrong with that request. Please try again.