Implement control over recursive iteration in #getItems()

Closes .
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
src

View file

@ -36,6 +36,17 @@ Base.inject(/** @lends Base# */{
}, []).join(', ') + ' }'; }, []).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 * The class name of the object as a string, if the prototype defines a
* `_class` value. * `_class` value.

View file

@ -1879,6 +1879,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
} }
} else if (type === 'function') { } else if (type === 'function') {
return name(this); return name(this);
} else if (name === 'match') {
return compare(this);
} else { } else {
var value = /^(empty|editable)$/.test(name) var value = /^(empty|editable)$/.test(name)
// Handle boolean test functions separately, by calling them // Handle boolean test functions separately, by calling them
@ -1886,21 +1888,24 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
? this['is' + Base.capitalize(name)]() ? this['is' + Base.capitalize(name)]()
// Support legacy Item#type property to match hyphenated // Support legacy Item#type property to match hyphenated
// class-names. // class-names.
// TODO: Remove after December 2016.
: name === 'type' : name === 'type'
? Base.hyphenate(this._class) ? Base.hyphenate(this._class)
: this[name]; : this[name];
if (/^(constructor|class)$/.test(name)) { if (/^(constructor|class)$/.test(name)) {
if (!(this instanceof compare)) if (typeof compare === 'function') {
return false; return this instanceof compare;
} else if (compare instanceof RegExp) { } else {
if (!compare.test(value)) // Compare further with the _class property value instead.
return false; value = this._class;
}
}
if (compare instanceof RegExp) {
return compare.test(value);
} else if (typeof compare === 'function') { } else if (typeof compare === 'function') {
if (!compare(value)) return !!compare(value);
return false;
} else if (Base.isPlainObject(compare)) { } else if (Base.isPlainObject(compare)) {
if (!matchObject(compare, value)) return matchObject(compare, value);
return false;
} else if (!Base.equals(value, compare)) { } else if (!Base.equals(value, compare)) {
return false; return false;
} }
@ -1925,10 +1930,18 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
* See {@link Project#getItems(match)} for a selection of illustrated * See {@link Project#getItems(match)} for a selection of illustrated
* examples. * 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 * @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 * @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 * @param {Object|Function} match the criteria to match against
* @return {Item[]} the list of matching descendant items * @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 // Set up a couple of "side-car" values for the recursive calls
// of _getItems below, mainly related to the handling of // of _getItems below, mainly related to the handling of
// inside / overlapping: // inside / overlapping:
var obj = typeof match === 'object' && match || {}, var obj = typeof match === 'object' && match,
overlapping = obj.overlapping, overlapping = obj && obj.overlapping,
inside = obj.inside, inside = obj && obj.inside,
// If overlapping is set, we also perform the inside check: // If overlapping is set, we also perform the inside check:
bounds = overlapping || inside, bounds = overlapping || inside,
rect = bounds && Rectangle.read([bounds]); rect = bounds && Rectangle.read([bounds]);
param = { param = {
items: [], // The list to contain the results. items: [], // The list to contain the results.
recursive: obj && obj.recursive !== false,
inside: !!inside, inside: !!inside,
overlapping: !!overlapping, overlapping: !!overlapping,
rect: rect, rect: rect,
@ -1983,14 +1997,16 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
insert: false insert: false
}) })
}; };
// Create a copy of the match object that doesn't contain the if (obj) {
// `inside` and `overlapping` properties. // Create a copy of the match object that doesn't contain
if (bounds) // these special properties:
match = Base.set({}, match, match = Base.set({}, match, {
{ inside: true, overlapping: true }); recursive: true, inside: true, overlapping: true
});
}
} }
var items = param && param.items, var items = param.items,
rect = param && param.rect; rect = param.rect;
matrix = rect && (matrix || new Matrix()); matrix = rect && (matrix || new Matrix());
for (var i = 0, l = children && children.length; i < l; i++) { for (var i = 0, l = children && children.length; i < l; i++) {
var child = children[i], var child = children[i],
@ -2016,9 +2032,11 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
if (firstOnly) if (firstOnly)
break; break;
} }
_getItems(child._children, match, if (param.recursive !== false) {
childMatrix, param, _getItems(child._children, match,
firstOnly); childMatrix, param,
firstOnly);
}
if (firstOnly && items.length > 0) if (firstOnly && items.length > 0)
break; break;
} }

View file

@ -352,20 +352,28 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* *
* Fetch items contained within the project whose properties match the * Fetch items contained within the project whose properties match the
* criteria in the specified object. * criteria in the specified object.
* Extended matching is possible by providing a compare function or * Extended matching of properties is possible by providing a comparator
* regular expression. Matching points, colors only work as a comparison * function or regular expression. Matching points, colors only work as a
* of the full object, not partial matching (e.g. only providing the x- * comparison of the full object, not partial matching (e.g. only providing
* coordinate to match all points with that x-value). Partial matching * the x- coordinate to match all points with that x-value). Partial
* does work for {@link Item#data}. * matching does work for {@link Item#data}.
* Matching items against a rectangular area is also possible, by setting * Matching items against a rectangular area is also possible, by setting
* either {@code match.inside} or {@code match.overlapping} to a rectangle * 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 * describing the area in which the items either have to be fully or partly
* contained. * 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 * @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 * @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: * @example {@paperscript} // Fetch all selected path items:
* var path1 = new Path.Circle({ * var path1 = new Path.Circle({
@ -434,7 +442,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* // Select the fetched path: * // Select the fetched path:
* items[0].selected = true; * 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: * // Create a circle shaped path:
* var path1 = new Path.Circle({ * var path1 = new Path.Circle({
@ -461,7 +469,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* // Select the fetched item: * // Select the fetched item:
* items[0].selected = true; * 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): * // Create a rectangle shaped path (4 segments):
* var path1 = new Path.Rectangle({ * var path1 = new Path.Rectangle({