Implement Base.readSupported() and improve argument reading in Shape

This commit is contained in:
Jürg Lehni 2019-12-15 14:34:46 +01:00
parent 2b62eb5cfa
commit dacfce0498
3 changed files with 82 additions and 34 deletions

View file

@ -98,11 +98,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
arg0.width || 0, arg0.height || 0); arg0.width || 0, arg0.height || 0);
read = 1; read = 1;
} else if (arg0.from === undefined && arg0.to === undefined) { } else if (arg0.from === undefined && arg0.to === undefined) {
// Use Base.filter() to support whatever property the rectangle // Use `Base.readSupported()` to read and consume whatever
// can take, but handle from/to separately below. // property the rectangle can receive, but handle `from` / `to`
// separately below.
this._set(0, 0, 0, 0); this._set(0, 0, 0, 0);
Base.filter(this, arg0); if (Base.readSupported(arguments, this)) {
read = 1; read = 1;
}
} }
} }
if (read === undefined) { if (read === undefined) {
@ -141,13 +143,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
} }
this._set(x, y, width, height); this._set(x, y, width, height);
read = arguments.__index; 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) if (this.__read)
this.__read = read; this.__read = read;
return this; return this;

View file

@ -269,11 +269,11 @@ statics: /** @lends Base */{
}, },
/** /**
* Allows using of Base.read() mechanism in combination with reading named * Allows using of `Base.read()` mechanism in combination with reading named
* arguments form a passed property object literal. Calling Base.readNamed() * arguments form a passed property object literal. Calling
* can read both from such named properties and normal unnamed arguments * `Base.readNamed()` can read both from such named properties and normal
* through Base.read(). In use for example for the various * unnamed arguments through `Base.read()`. In use for example for
* Path.Constructors. * the various `Path` constructors in `Path.Constructors.js`.
* *
* @param {Array} list the list to read from, either an arguments object or * @param {Array} list the list to read from, either an arguments object or
* a normal array * a normal array
@ -287,24 +287,68 @@ statics: /** @lends Base */{
*/ */
readNamed: function(list, name, start, options, amount) { readNamed: function(list, name, start, options, amount) {
var value = this.getNamed(list, name), var value = this.getNamed(list, name),
hasObject = value !== undefined; hasValue = value !== undefined;
if (hasObject) { if (hasValue) {
// Create a _filtered object that inherits from list[0], and // Create a _filtered object that inherits from `source`, and
// override all fields that were already read with undefined. // override all fields that were already read with undefined.
var filtered = list.__filtered; var filtered = list.__filtered;
if (!filtered) { if (!filtered) {
filtered = list.__filtered = Base.create(list[0]); var source = this.getSource(list);
// Point _unfiltered to the original so Base#_set() can filtered = list.__filtered = Base.create(source);
// execute hasOwnProperty on it. // Point __unfiltered to the original, so `Base.filter()` can
filtered.__unfiltered = list[0]; // use it to get all keys to iterate over.
filtered.__unfiltered = source;
} }
// delete wouldn't work since the masked parent's value would // delete wouldn't work since the masked parent's value would
// shine through. // shine through.
filtered[name] = undefined; filtered[name] = undefined;
} }
var l = hasObject ? [value] : list, return this.read(hasValue ? [value] : list, start, options, amount);
res = this.read(l, start, options, amount); },
return res;
/**
* 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 * provided, it returns the whole arguments object
*/ */
getNamed: function(list, name) { getNamed: function(list, name) {
var arg = list[0]; var source = this.getSource(list);
if (list._hasObject === undefined) if (source) {
list._hasObject = list.length === 1 && Base.isPlainObject(arg);
if (list._hasObject)
// Return the whole arguments object if no name is provided. // Return the whole arguments object if no name is provided.
return name ? arg[name] : list.__filtered || arg; return name ? source[name] : list.__filtered || source;
}
}, },
/** /**

View file

@ -82,7 +82,7 @@ var Shape = Item.extend(/** @lends Shape# */{
setSize: function(/* size */) { setSize: function(/* size */) {
var size = Size.read(arguments); var size = Size.read(arguments);
if (!this._size) { if (!this._size) {
// First time, e.g. whean reading from JSON... // First time, e.g. when reading from JSON...
this._size = size.clone(); this._size = size.clone();
} else if (!this._size.equals(size)) { } else if (!this._size.equals(size)) {
var type = this._type, var type = this._type,
@ -101,8 +101,8 @@ var Shape = Item.extend(/** @lends Shape# */{
this._radius._set(width / 2, height / 2); this._radius._set(width / 2, height / 2);
} }
this._size._set(width, height); this._size._set(width, height);
this._changed(/*#=*/Change.GEOMETRY);
} }
this._changed(/*#=*/Change.GEOMETRY);
}, },
/** /**
@ -130,7 +130,7 @@ var Shape = Item.extend(/** @lends Shape# */{
} else { } else {
radius = Size.read(arguments); radius = Size.read(arguments);
if (!this._radius) { if (!this._radius) {
// First time, e.g. whean reading from JSON... // First time, e.g. when reading from JSON...
this._radius = radius.clone(); this._radius = radius.clone();
} else { } else {
if (this._radius.equals(radius)) 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: // Mess with indentation in order to get more line-space below:
statics: new function() { statics: new function() {
function createShape(type, point, size, radius, args) { 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._type = type;
item._size = size; item._size = size;
item._radius = radius; item._radius = radius;
item._initialize(Base.getNamed(args), point);
return item; return item;
} }