Implement control over recursive iteration in #getItems()

Closes #853.
This commit is contained in:
Jürg Lehni 2015-12-27 14:23:19 +01:00
parent f95d6ab310
commit de532aac2f
3 changed files with 70 additions and 33 deletions

View file

@ -36,6 +36,17 @@ Base.inject(/** @lends Base# */{
}, []).join(', ') + ' }';
},
/**
* The class of the object as a constructor function.
* The same as {@link #constructor}.
*
* @beans
*/
getClass: function() {
// This is mainly here to create symmetry with the the #getItems() API.
return this.constructor;
},
/**
* The class name of the object as a string, if the prototype defines a
* `_class` value.

View file

@ -1879,6 +1879,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
}
} else if (type === 'function') {
return name(this);
} else if (name === 'match') {
return compare(this);
} else {
var value = /^(empty|editable)$/.test(name)
// Handle boolean test functions separately, by calling them
@ -1886,21 +1888,24 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
? this['is' + Base.capitalize(name)]()
// Support legacy Item#type property to match hyphenated
// class-names.
// TODO: Remove after December 2016.
: name === 'type'
? Base.hyphenate(this._class)
: this[name];
if (/^(constructor|class)$/.test(name)) {
if (!(this instanceof compare))
return false;
} else if (compare instanceof RegExp) {
if (!compare.test(value))
return false;
if (typeof compare === 'function') {
return this instanceof compare;
} else {
// Compare further with the _class property value instead.
value = this._class;
}
}
if (compare instanceof RegExp) {
return compare.test(value);
} else if (typeof compare === 'function') {
if (!compare(value))
return false;
return !!compare(value);
} else if (Base.isPlainObject(compare)) {
if (!matchObject(compare, value))
return false;
return matchObject(compare, value);
} else if (!Base.equals(value, compare)) {
return false;
}
@ -1925,10 +1930,18 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
* See {@link Project#getItems(match)} for a selection of illustrated
* examples.
*
* @option [match.recursive=true] {Boolean} whether to loop recursively
* through all children, or stop at the current level
* @option match.match {Function} a match function to be called for each
* item, allowing the definition of more flexible item checks that are
* not bound to properties. If no other match properties are defined,
* this function can also be passed instead of the {@code match} object
* @option match.class {Function} the constructor function of the item type
* to match against
* @option match.inside {Rectangle} the rectangle in which the items need to
* be fully contained
* be fully contained
* @option match.overlapping {Rectangle} the rectangle with which the items
* need to at least partly overlap
* need to at least partly overlap
*
* @param {Object|Function} match the criteria to match against
* @return {Item[]} the list of matching descendant items
@ -1967,14 +1980,15 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
// Set up a couple of "side-car" values for the recursive calls
// of _getItems below, mainly related to the handling of
// inside / overlapping:
var obj = typeof match === 'object' && match || {},
overlapping = obj.overlapping,
inside = obj.inside,
var obj = typeof match === 'object' && match,
overlapping = obj && obj.overlapping,
inside = obj && obj.inside,
// If overlapping is set, we also perform the inside check:
bounds = overlapping || inside,
rect = bounds && Rectangle.read([bounds]);
param = {
items: [], // The list to contain the results.
recursive: obj && obj.recursive !== false,
inside: !!inside,
overlapping: !!overlapping,
rect: rect,
@ -1983,14 +1997,16 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
insert: false
})
};
// Create a copy of the match object that doesn't contain the
// `inside` and `overlapping` properties.
if (bounds)
match = Base.set({}, match,
{ inside: true, overlapping: true });
if (obj) {
// Create a copy of the match object that doesn't contain
// these special properties:
match = Base.set({}, match, {
recursive: true, inside: true, overlapping: true
});
}
}
var items = param && param.items,
rect = param && param.rect;
var items = param.items,
rect = param.rect;
matrix = rect && (matrix || new Matrix());
for (var i = 0, l = children && children.length; i < l; i++) {
var child = children[i],
@ -2016,9 +2032,11 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
if (firstOnly)
break;
}
_getItems(child._children, match,
childMatrix, param,
firstOnly);
if (param.recursive !== false) {
_getItems(child._children, match,
childMatrix, param,
firstOnly);
}
if (firstOnly && items.length > 0)
break;
}

View file

@ -352,20 +352,28 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
*
* Fetch items contained within the project whose properties match the
* criteria in the specified object.
* Extended matching is possible by providing a compare function or
* regular expression. Matching points, colors only work as a comparison
* of the full object, not partial matching (e.g. only providing the x-
* coordinate to match all points with that x-value). Partial matching
* does work for {@link Item#data}.
* Extended matching of properties is possible by providing a comparator
* function or regular expression. Matching points, colors only work as a
* comparison of the full object, not partial matching (e.g. only providing
* the x- coordinate to match all points with that x-value). Partial
* matching does work for {@link Item#data}.
* Matching items against a rectangular area is also possible, by setting
* either {@code match.inside} or {@code match.overlapping} to a rectangle
* describing the area in which the items either have to be fully or partly
* contained.
*
* @option [match.recursive=true] {Boolean} whether to loop recursively
* through all children, or stop at the current level
* @option match.match {Function} a match function to be called for each
* item, allowing the definition of more flexible item checks that are
* not bound to properties. If no other match properties are defined,
* this function can also be passed instead of the {@code match} object
* @option match.class {Function} the constructor function of the item type
* to match against
* @option match.inside {Rectangle} the rectangle in which the items need to
* be fully contained
* be fully contained
* @option match.overlapping {Rectangle} the rectangle with which the items
* need to at least partly overlap
* need to at least partly overlap
*
* @example {@paperscript} // Fetch all selected path items:
* var path1 = new Path.Circle({
@ -434,7 +442,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* // Select the fetched path:
* items[0].selected = true;
*
* @example {@paperscript} // Fetch items using a comparing function:
* @example {@paperscript} // Fetch items using a comparator function:
*
* // Create a circle shaped path:
* var path1 = new Path.Circle({
@ -461,7 +469,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* // Select the fetched item:
* items[0].selected = true;
*
* @example {@paperscript} // Fetch items using a comparing function (2):
* @example {@paperscript} // Fetch items using a comparator function (2):
*
* // Create a rectangle shaped path (4 segments):
* var path1 = new Path.Rectangle({