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,13 +98,15 @@ 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);
if (Base.readSupported(arguments, this)) {
read = 1;
}
}
}
if (read === undefined) {
// Read a point argument and look at the next value to see whether
// it's a size or a point, then read accordingly.
@ -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;
}
if (this.__read)
this.__read = read;
return this;

View file

@ -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;
}
},
/**

View file

@ -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);
}
},
/**
@ -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;
}