Define mechanism for flexible reading of named arguments through Base.readNamed() and Base.hasNamed(), and use it to implement property object literal versions of Path.Constructor code.

This commit is contained in:
Jürg Lehni 2012-12-30 18:24:33 +01:00
parent d0fff09bb0
commit 8bed8cb15d
3 changed files with 138 additions and 56 deletions

View file

@ -68,9 +68,9 @@ this.Base = Base.inject(/** @lends Base# */{
}, },
/** /**
* Checks if two values or objects are equals to each other, by using their * Checks if two values or objects are equals to each other, by using
* equals() methods if available, and also comparing elements of arrays * their equals() methods if available, and also comparing elements of
* and properties of objects. * arrays and properties of objects.
*/ */
equals: function(obj1, obj2) { equals: function(obj1, obj2) {
if (obj1 == obj2) if (obj1 == obj2)
@ -117,6 +117,8 @@ this.Base = Base.inject(/** @lends Base# */{
* from the apssed arguments list or array. * from the apssed arguments list or array.
* This is used in argument conversion, e.g. by all basic types (Point, * This is used in argument conversion, e.g. by all basic types (Point,
* Size, Rectangle) and also higher classes such as Color and Segment. * Size, Rectangle) and also higher classes such as Color and Segment.
* @param {Array} list the list to read from, either an arguments object
* or a normal array.
* @param {Number} start the index at which to start reading in the list * @param {Number} start the index at which to start reading in the list
* @param {Number} length the amount of elements that can be read * @param {Number} length the amount of elements that can be read
* @param {Boolean} clone controls wether passed objects should be * @param {Boolean} clone controls wether passed objects should be
@ -161,6 +163,13 @@ this.Base = Base.inject(/** @lends Base# */{
return obj; return obj;
}, },
/**
* Allows peeking ahead in reading of values and objects from arguments
* list through Base.read().
* @param {Array} list the list to read from, either an arguments object
* or a normal array.
* @param {Number} start the index at which to start reading in the list
*/
peek: function(list, start) { peek: function(list, start) {
return list[list._index = start || list._index || 0]; return list[list._index = start || list._index || 0];
}, },
@ -168,6 +177,8 @@ this.Base = Base.inject(/** @lends Base# */{
/** /**
* Reads all readable arguments from the list, handling nested arrays * Reads all readable arguments from the list, handling nested arrays
* seperately. * seperately.
* @param {Array} list the list to read from, either an arguments object
* or a normal array.
* @param {Number} start the index at which to start reading in the list * @param {Number} start the index at which to start reading in the list
* @param {Boolean} clone controls wether passed objects should be * @param {Boolean} clone controls wether passed objects should be
* cloned if they are already provided in the required type * cloned if they are already provided in the required type
@ -182,6 +193,54 @@ this.Base = Base.inject(/** @lends Base# */{
return res; return res;
}, },
/**
* 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.
* @param {Array} list the list to read from, either an arguments object
* or a normal array.
* @param {Number} start the index at which to start reading in the list
* @param {String} name the property name to read from.
* @param {Boolean} [filter=true] controls wether a clone of the passed
* object should be kept in list._filtered, of which the consumed
* properties are removed. This can be passed on e.g. to Item#set().
*/
readNamed: function(list, name, filter) {
var value = this.getNamed(list, name),
// value is undefined if there is no arguments object, and null
// if there is one, but no value is defined.
hasObject = value !== undefined;
if (hasObject && filter !== false) {
if (!list._filtered)
list._filtered = Base.merge(list[0]);
delete list._filtered[name];
}
return this.read(hasObject ? [value] : list);
},
/**
* @return the named value if the list provides an arguments object,
* null if the named value is null or undefined, and undefined if there
* is no 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) {
value = arg[name];
// Convert undefined to null, to distinguish from undefined
// result, when there is no arguments object.
return value !== undefined ? value : null;
}
},
hasNamed: function(list, name) {
return !!this.getNamed(list, name);
},
/** /**
* Serializes the passed object into a format that can be passed to * Serializes the passed object into a format that can be passed to
* JSON.stringify() for JSON serialization. * JSON.stringify() for JSON serialization.

View file

@ -132,9 +132,11 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* the item it is called on, and returns the item itself. * the item it is called on, and returns the item itself.
*/ */
set: function(props) { set: function(props) {
for (var key in props) if (props) {
if (props.hasOwnProperty(key)) for (var key in props)
this[key] = props[key]; if (props.hasOwnProperty(key))
this[key] = props[key];
}
return this; return this;
}, },

View file

@ -16,13 +16,29 @@
Path.inject({ statics: new function() { Path.inject({ statics: new function() {
function createRectangle(rect) { function readRectangle(list) {
rect = Rectangle.read(arguments); var rect;
var left = rect.x, if (Base.hasNamed(list, 'from')) {
top = rect.y, rect = new Rectangle(Point.readNamed(list, 'from'),
right = left + rect.width, Point.readNamed(list, 'to'));
bottom = top + rect.height, } else if (Base.hasNamed(list, 'size')) {
path = new Path(); rect = new Rectangle(Point.readNamed(list, 'point'),
Size.readNamed(list, 'size'));
if (Base.hasNamed(list, 'center'))
rect.setCenter(Point.readNamed(list, 'center'));
} else {
rect = Rectangle.readNamed(list, 'rectangle');
}
return rect;
}
function createRectangle() {
var rect = readRectangle(arguments),
left = rect.getLeft(),
top = rect.getTop(),
right = rect.getRight(),
bottom = rect.getBottom(),
path = new Path(arguments._filtered);
path._add([ path._add([
new Segment(Point.create(left, bottom)), new Segment(Point.create(left, bottom)),
new Segment(Point.create(left, top)), new Segment(Point.create(left, top)),
@ -43,12 +59,13 @@ Path.inject({ statics: new function() {
new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0]) new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])
]; ];
function createEllipse(rect) { function createEllipse() {
rect = Rectangle.read(arguments); var rect = readRectangle(arguments),
var path = new Path(), path = new Path(arguments._filtered),
point = rect.getPoint(true), point = rect.getPoint(true),
size = rect.getSize(true), size = rect.getSize(true),
segments = new Array(4); segments = new Array(4);
console.log(JSON.stringify(arguments._filtered))
for (var i = 0; i < 4; i++) { for (var i = 0; i < 4; i++) {
var segment = ellipseSegments[i]; var segment = ellipseSegments[i];
segments[i] = new Segment( segments[i] = new Segment(
@ -68,8 +85,8 @@ Path.inject({ statics: new function() {
* *
* Creates a Path Item with two anchor points forming a line. * Creates a Path Item with two anchor points forming a line.
* *
* @param {Point} pt1 the first anchor point of the path * @param {Point} from the first anchor point of the path
* @param {Point} pt2 the second anchor point of the path * @param {Point} to the second anchor point of the path
* @return {Path} the newly created path * @return {Path} the newly created path
* *
* @example * @example
@ -80,9 +97,9 @@ Path.inject({ statics: new function() {
*/ */
Line: function() { Line: function() {
return new Path( return new Path(
Point.read(arguments), Point.readNamed(arguments, 'from'),
Point.read(arguments) Point.readNamed(arguments, 'to')
); ).set(arguments._filtered);
}, },
/** /**
@ -106,8 +123,8 @@ Path.inject({ statics: new function() {
* constructor figures out how to fit a rectangle between them. * constructor figures out how to fit a rectangle between them.
* *
* @name Path.Rectangle * @name Path.Rectangle
* @param {Point} point1 The first point defining the rectangle * @param {Point} from The first point defining the rectangle
* @param {Point} point2 The second point defining the rectangle * @param {Point} to The second point defining the rectangle
* @return {Path} the newly created path * @return {Path} the newly created path
* *
* @example * @example
@ -120,7 +137,7 @@ Path.inject({ statics: new function() {
* Creates a rectangle shaped Path Item from the passed abstract * Creates a rectangle shaped Path Item from the passed abstract
* {@link Rectangle}. * {@link Rectangle}.
* *
* @param {Rectangle} rect * @param {Rectangle} rectangle
* @return {Path} the newly created path * @return {Path} the newly created path
* *
* @example * @example
@ -135,8 +152,8 @@ Path.inject({ statics: new function() {
/** /**
* Creates a rectangular Path Item with rounded corners. * Creates a rectangular Path Item with rounded corners.
* *
* @param {Rectangle} rect * @param {Rectangle} rectangle
* @param {Size} size the size of the rounded corners * @param {Size} radius the size of the rounded corners
* @return {Path} the newly created path * @return {Path} the newly created path
* *
* @example * @example
@ -146,30 +163,30 @@ Path.inject({ statics: new function() {
* var cornerSize = new Size(30, 30); * var cornerSize = new Size(30, 30);
* var path = new Path.RoundRectangle(rectangle, cornerSize); * var path = new Path.RoundRectangle(rectangle, cornerSize);
*/ */
RoundRectangle: function(rect, size) { RoundRectangle: function(rect, radius) {
var _rect = Rectangle.read(arguments), var _rect = Rectangle.readNamed(arguments, 'rectangle'),
_size = Size.read(arguments); _radius = Size.readNamed(arguments, 'radius');
if (_size.isZero()) if (_radius.isZero())
return createRectangle(rect); return createRectangle(rect);
_size = Size.min(_size, _rect.getSize(true).divide(2)); _radius = Size.min(_radius, _rect.getSize(true).divide(2));
var bl = _rect.getBottomLeft(true), var bl = _rect.getBottomLeft(true),
tl = _rect.getTopLeft(true), tl = _rect.getTopLeft(true),
tr = _rect.getTopRight(true), tr = _rect.getTopRight(true),
br = _rect.getBottomRight(true), br = _rect.getBottomRight(true),
uSize = _size.multiply(kappa * 2), h = _radius.multiply(kappa * 2), // handle vector
path = new Path(); path = new Path();
path._add([ path._add([
new Segment(bl.add(_size.width, 0), null, [-uSize.width, 0]), new Segment(bl.add(_radius.width, 0), null, [-h.width, 0]),
new Segment(bl.subtract(0, _size.height), [0, uSize.height], null), new Segment(bl.subtract(0, _radius.height), [0, h.height], null),
new Segment(tl.add(0, _size.height), null, [0, -uSize.height]), new Segment(tl.add(0, _radius.height), null, [0, -h.height]),
new Segment(tl.add(_size.width, 0), [-uSize.width, 0], null), new Segment(tl.add(_radius.width, 0), [-h.width, 0], null),
new Segment(tr.subtract(_size.width, 0), null, [uSize.width, 0]), new Segment(tr.subtract(_radius.width, 0), null, [h.width, 0]),
new Segment(tr.add(0, _size.height), [0, -uSize.height], null), new Segment(tr.add(0, _radius.height), [0, -h.height], null),
new Segment(br.subtract(0, _size.height), null, [0, uSize.height]), new Segment(br.subtract(0, _radius.height), null, [0, h.height]),
new Segment(br.subtract(_size.width, 0), [uSize.width, 0], null) new Segment(br.subtract(_radius.width, 0), [h.width, 0], null)
]); ]);
path._closed = true; path._closed = true;
return path; return path;
@ -178,7 +195,7 @@ Path.inject({ statics: new function() {
/** /**
* Creates an ellipse shaped Path Item. * Creates an ellipse shaped Path Item.
* *
* @param {Rectangle} rect * @param {Rectangle} rectangle
* @param {Boolean} [circumscribed=false] when set to {@code true} the * @param {Boolean} [circumscribed=false] when set to {@code true} the
* ellipse shaped path will be created so the rectangle fits into * ellipse shaped path will be created so the rectangle fits into
* it. When set to {@code false} the ellipse path will fit within * it. When set to {@code false} the ellipse path will fit within
@ -210,10 +227,11 @@ Path.inject({ statics: new function() {
* var path = new Path.Circle(new Point(100, 100), 50); * var path = new Path.Circle(new Point(100, 100), 50);
*/ */
Circle: function(center, radius) { Circle: function(center, radius) {
var _center = Point.read(arguments), var _center = Point.readNamed(arguments, 'center'),
_radius = Base.read(arguments); _radius = Base.readNamed(arguments, 'radius');
return createEllipse(new Rectangle(_center.subtract(_radius), return createEllipse(new Rectangle(_center.subtract(_radius),
Size.create(_radius * 2, _radius * 2))); Size.create(_radius * 2, _radius * 2)))
.set(arguments._filtered);
}, },
/** /**
@ -232,9 +250,12 @@ Path.inject({ statics: new function() {
* path.strokeColor = 'black'; * path.strokeColor = 'black';
*/ */
Arc: function(from, through, to) { Arc: function(from, through, to) {
var path = new Path(); var _from = Point.readNamed(arguments, 'from'),
path.moveTo(from); _through = Point.readNamed(arguments, 'through'),
path.arcTo(through, to); _to = Point.readNamed(arguments, 'to'),
path = new Path(arguments._filtered);
path.moveTo(_from);
path.arcTo(_through, _to);
return path; return path;
}, },
@ -263,10 +284,10 @@ Path.inject({ statics: new function() {
* decahedron.fillColor = 'black'; * decahedron.fillColor = 'black';
*/ */
RegularPolygon: function(center, numSides, radius) { RegularPolygon: function(center, numSides, radius) {
var _center = Point.read(arguments), var _center = Point.readNamed(arguments, 'center'),
_numSides = Base.read(arguments), _numSides = Base.readNamed(arguments, 'numSides'),
_radius = Base.read(arguments), _radius = Base.readNamed(arguments, 'radius'),
path = new Path(), path = new Path(arguments._filtered),
step = 360 / _numSides, step = 360 / _numSides,
three = !(_numSides % 3), three = !(_numSides % 3),
vector = new Point(0, three ? -_radius : _radius), vector = new Point(0, three ? -_radius : _radius),
@ -303,11 +324,11 @@ Path.inject({ statics: new function() {
* path.fillColor = 'black'; * path.fillColor = 'black';
*/ */
Star: function(center, numPoints, radius1, radius2) { Star: function(center, numPoints, radius1, radius2) {
var _center = Point.read(arguments), var _center = Point.readNamed(arguments, 'center'),
_numPoints = Base.read(arguments) * 2, _numPoints = Base.readNamed(arguments, 'numPoints') * 2,
_radius1 = Base.read(arguments), _radius1 = Base.readNamed(arguments, 'radius1'),
_radius2 = Base.read(arguments), _radius2 = Base.readNamed(arguments, 'radius2'),
path = new Path(), path = new Path(arguments._filtered),
step = 360 / _numPoints, step = 360 / _numPoints,
vector = new Point(0, -1), vector = new Point(0, -1),
segments = new Array(_numPoints); segments = new Array(_numPoints);