From dacfce049805a8fd19f6db1c26a64f1227e0d4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sun, 15 Dec 2019 14:34:46 +0100 Subject: [PATCH] Implement Base.readSupported() and improve argument reading in Shape --- src/basic/Rectangle.js | 22 ++++++----- src/core/Base.js | 83 ++++++++++++++++++++++++++++++++---------- src/item/Shape.js | 11 ++++-- 3 files changed, 82 insertions(+), 34 deletions(-) diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js index 09a388aa..bd104ee6 100644 --- a/src/basic/Rectangle.js +++ b/src/basic/Rectangle.js @@ -98,11 +98,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ arg0.width || 0, arg0.height || 0); read = 1; } else if (arg0.from === undefined && arg0.to === undefined) { - // Use Base.filter() to support whatever property the rectangle - // can take, but handle from/to separately below. + // Use `Base.readSupported()` to read and consume whatever + // property the rectangle can receive, but handle `from` / `to` + // separately below. this._set(0, 0, 0, 0); - Base.filter(this, arg0); - read = 1; + if (Base.readSupported(arguments, this)) { + read = 1; + } } } if (read === undefined) { @@ -141,13 +143,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ } this._set(x, y, width, height); read = arguments.__index; - // arguments.__filtered wouldn't survive the function call even if a - // previous arguments list was passed through Function#apply(). - // Return it on the object instead, see Base.read() - var filtered = arguments.__filtered; - if (filtered) - this.__filtered = filtered; } + // arguments.__filtered wouldn't survive the function call even if a + // previous arguments list was passed through Function#apply(). + // Return it on the object instead, see Base.read() + var filtered = arguments.__filtered; + if (filtered) + this.__filtered = filtered; if (this.__read) this.__read = read; return this; diff --git a/src/core/Base.js b/src/core/Base.js index 52d963af..dcb668ef 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -269,11 +269,11 @@ statics: /** @lends Base */{ }, /** - * Allows using of Base.read() mechanism in combination with reading named - * arguments form a passed property object literal. Calling Base.readNamed() - * can read both from such named properties and normal unnamed arguments - * through Base.read(). In use for example for the various - * Path.Constructors. + * Allows using of `Base.read()` mechanism in combination with reading named + * arguments form a passed property object literal. Calling + * `Base.readNamed()` can read both from such named properties and normal + * unnamed arguments through `Base.read()`. In use for example for + * the various `Path` constructors in `Path.Constructors.js`. * * @param {Array} list the list to read from, either an arguments object or * a normal array @@ -287,24 +287,68 @@ statics: /** @lends Base */{ */ readNamed: function(list, name, start, options, amount) { var value = this.getNamed(list, name), - hasObject = value !== undefined; - if (hasObject) { - // Create a _filtered object that inherits from list[0], and + hasValue = value !== undefined; + if (hasValue) { + // Create a _filtered object that inherits from `source`, and // override all fields that were already read with undefined. var filtered = list.__filtered; if (!filtered) { - filtered = list.__filtered = Base.create(list[0]); - // Point _unfiltered to the original so Base#_set() can - // execute hasOwnProperty on it. - filtered.__unfiltered = list[0]; + var source = this.getSource(list); + filtered = list.__filtered = Base.create(source); + // Point __unfiltered to the original, so `Base.filter()` can + // use it to get all keys to iterate over. + filtered.__unfiltered = source; } // delete wouldn't work since the masked parent's value would // shine through. filtered[name] = undefined; } - var l = hasObject ? [value] : list, - res = this.read(l, start, options, amount); - return res; + return this.read(hasValue ? [value] : list, start, options, amount); + }, + + /** + * If `list[0]` is a source object, calls `Base.readNamed()` for each key in + * it that is supported on `dest`, consuming these values. + * + * @param {Array} list the list to read from, either an arguments object or + * a normal array + * @param {Object} dest the object on which to set the supported properties + * @return {Boolean} {@true if any property was read from the source object} + */ + readSupported: function(list, dest) { + var source = this.getSource(list), + that = this, + read = false; + if (source) { + // If `source` is a filtered object, we get the keys from the the + // original object (it's parent / prototype). See _filtered + // inheritance trick in the argument reading code. + Object.keys(source).forEach(function(key) { + if (key in dest) { + var value = that.readNamed(list, key); + // Due to the _filtered inheritance trick, undefined is used + // to mask already consumed named arguments. + if (value !== undefined) { + dest[key] = value; + } + read = true; + } + }); + } + return read; + }, + + /** + * @return the arguments object if the list provides one at `list[0]` + */ + getSource: function(list) { + var source = list.__source; + if (source === undefined) { + var arg = list.length === 1 && list[0]; + source = list.__source = arg && Base.isPlainObject(arg) + ? arg : null; + } + return source; }, /** @@ -314,12 +358,11 @@ statics: /** @lends Base */{ * provided, it returns the whole arguments object */ getNamed: function(list, name) { - var arg = list[0]; - if (list._hasObject === undefined) - list._hasObject = list.length === 1 && Base.isPlainObject(arg); - if (list._hasObject) + var source = this.getSource(list); + if (source) { // Return the whole arguments object if no name is provided. - return name ? arg[name] : list.__filtered || arg; + return name ? source[name] : list.__filtered || source; + } }, /** diff --git a/src/item/Shape.js b/src/item/Shape.js index 63bf40e2..e9b7e340 100644 --- a/src/item/Shape.js +++ b/src/item/Shape.js @@ -82,7 +82,7 @@ var Shape = Item.extend(/** @lends Shape# */{ setSize: function(/* size */) { var size = Size.read(arguments); if (!this._size) { - // First time, e.g. whean reading from JSON... + // First time, e.g. when reading from JSON... this._size = size.clone(); } else if (!this._size.equals(size)) { var type = this._type, @@ -101,8 +101,8 @@ var Shape = Item.extend(/** @lends Shape# */{ this._radius._set(width / 2, height / 2); } this._size._set(width, height); + this._changed(/*#=*/Change.GEOMETRY); } - this._changed(/*#=*/Change.GEOMETRY); }, /** @@ -130,7 +130,7 @@ var Shape = Item.extend(/** @lends Shape# */{ } else { radius = Size.read(arguments); if (!this._radius) { - // First time, e.g. whean reading from JSON... + // First time, e.g. when reading from JSON... this._radius = radius.clone(); } else { if (this._radius.equals(radius)) @@ -390,10 +390,13 @@ new function() { // Scope for _contains() and _hitTestSelf() code. // Mess with indentation in order to get more line-space below: statics: new function() { function createShape(type, point, size, radius, args) { - var item = new Shape(Base.getNamed(args), point); + // Use `Base.create()` to avoid calling `initialize()` until after the + // internal fields are set here, then call `_initialize()` directly: + var item = Base.create(Shape.prototype); item._type = type; item._size = size; item._radius = radius; + item._initialize(Base.getNamed(args), point); return item; }