mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
346 lines
12 KiB
JavaScript
Executable file
346 lines
12 KiB
JavaScript
Executable file
/**
|
|
* straps.js - Class inheritance library with support for bean-style accessors
|
|
*
|
|
* Copyright (c) 2006 - 2013 Juerg Lehni
|
|
* http://lehni.org/
|
|
*
|
|
* Distributed under the MIT license.
|
|
*
|
|
* straps.js was created by extracting and simplifying the inheritance framework
|
|
* from boostrap.js, a JavaScript DOM library, also published by Juerg Lehni:
|
|
* https://github.com/lehni/bootstrap.js
|
|
*
|
|
* Inspirations:
|
|
* http://dean.edwards.name/weblog/2006/03/base/
|
|
* http://dev.helma.org/Wiki/JavaScript+Inheritance+Sugar/
|
|
*/
|
|
|
|
var Base = new function() {
|
|
var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,
|
|
toString = Object.prototype.toString,
|
|
proto = Array.prototype,
|
|
slice = proto.slice,
|
|
|
|
forEach = proto.forEach || function(iter, bind) {
|
|
for (var i = 0, l = this.length; i < l; i++)
|
|
iter.call(bind, this[i], i, this);
|
|
},
|
|
|
|
forIn = function(iter, bind) {
|
|
// Do not use Object.keys for iteration as iterators might modify
|
|
// the object we're iterating over, making the hasOwnProperty still
|
|
// necessary.
|
|
for (var i in this)
|
|
if (this.hasOwnProperty(i))
|
|
iter.call(bind, this[i], i, this);
|
|
},
|
|
|
|
isArray = Array.isArray = Array.isArray || function(obj) {
|
|
return toString.call(obj) === '[object Array]';
|
|
},
|
|
|
|
// A short-cut to a simplified version of Object.create that only
|
|
// supports the first parameter (in the emulation):
|
|
create = Object.create || function(proto) {
|
|
// From all browsers that do not offer Object.create(), we only
|
|
// support Firefox 3.5 & 3.6, and this hack works there:
|
|
return { __proto__: proto };
|
|
},
|
|
|
|
describe = Object.getOwnPropertyDescriptor || function(obj, name) {
|
|
// Emulate Object.getOwnPropertyDescriptor for outdated browsers
|
|
var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
|
|
return get
|
|
? { get: get, set: obj.__lookupSetter__(name),
|
|
enumerable: true, configurable: true }
|
|
: obj.hasOwnProperty(name)
|
|
? { value: obj[name], enumerable: true,
|
|
configurable: true, writable: true }
|
|
: null;
|
|
},
|
|
|
|
_define = Object.defineProperty || function(obj, name, desc) {
|
|
// Emulate Object.defineProperty for outdated browsers
|
|
if ((desc.get || desc.set) && obj.__defineGetter__) {
|
|
if (desc.get)
|
|
obj.__defineGetter__(name, desc.get);
|
|
if (desc.set)
|
|
obj.__defineSetter__(name, desc.set);
|
|
} else {
|
|
obj[name] = desc.value;
|
|
}
|
|
return obj;
|
|
},
|
|
|
|
define = function(obj, name, desc) {
|
|
// Both Safari and Chrome at one point ignored configurable = true
|
|
// and did not allow overriding of existing properties:
|
|
// https://code.google.com/p/chromium/issues/detail?id=72736
|
|
// https://bugs.webkit.org/show_bug.cgi?id=54289
|
|
// The workaround is to delete the property first.
|
|
// TODO: Remove this fix in July 2014, and use _define directly.
|
|
delete obj[name];
|
|
return _define(obj, name, desc);
|
|
};
|
|
|
|
/**
|
|
* Private function that injects functions from src into dest, overriding
|
|
* (and inherinting from) base.
|
|
*/
|
|
function inject(dest, src, enumerable, base, preserve, generics) {
|
|
var beans;
|
|
|
|
/**
|
|
* Private function that injects one field with given name and checks if
|
|
* the field is a function that needs to be wrapped for calls of base().
|
|
* This is only needed if the function in base is different from the one
|
|
* in src, and if the one in src is actually calling base through base.
|
|
* The string of the function is parsed for this.base to detect calls.
|
|
*/
|
|
function field(name, val, dontCheck, generics) {
|
|
// This does even work for prop: 0, as it will just be looked up
|
|
// again through describe.
|
|
var val = val || (val = describe(src, name))
|
|
&& (val.get ? val : val.value);
|
|
// Allow aliases to properties with different names, by having
|
|
// string values starting with '#'
|
|
if (typeof val === 'string' && val[0] === '#')
|
|
val = src[val.substring(1)] || val;
|
|
var isFunc = typeof val === 'function',
|
|
res = val,
|
|
// Only lookup previous value if we preserve or define a
|
|
// function that might need it for this.base(). If we're
|
|
// defining a getter, don't lookup previous value, but look if
|
|
// the property exists (name in dest) and store result in prev
|
|
prev = preserve || isFunc
|
|
? (val && val.get ? name in dest : dest[name]) : null,
|
|
bean;
|
|
if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
|
|
&& (!preserve || !prev)) {
|
|
// Expose the 'super' function (meaning the one this function is
|
|
// overriding) through #base:
|
|
if (isFunc && prev)
|
|
val.base = prev;
|
|
// Produce bean properties if getters are specified. This does
|
|
// not produce properties for setter-only properties. Just
|
|
// collect beans for now, and look them up in dest at the end of
|
|
// fields injection. This ensures base works for beans too, and
|
|
// inherits setters for redefined getters in subclasses. Only
|
|
// add getter beans if they do not expect arguments. Functions
|
|
// that should function both with optional arguments and as
|
|
// beans should not declare the parameters and use the arguments
|
|
// array internally instead.
|
|
if (isFunc && beans && val.length === 0
|
|
&& (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
|
|
beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
|
|
// No need to look up getter if this is a function already.
|
|
if (!res || isFunc || !res.get)
|
|
res = { value: res, writable: true };
|
|
// Only set/change configurable and enumerable if this field is
|
|
// configurable
|
|
if ((describe(dest, name)
|
|
|| { configurable: true }).configurable) {
|
|
res.configurable = true;
|
|
res.enumerable = enumerable;
|
|
}
|
|
define(dest, name, res);
|
|
}
|
|
if (generics && isFunc && (!preserve || !generics[name])) {
|
|
generics[name] = function(bind) {
|
|
// Do not call Array.slice generic here, as on Safari,
|
|
// this seems to confuse scopes (calling another
|
|
// generic from generic-producing code).
|
|
return bind && dest[name].apply(bind,
|
|
slice.call(arguments, 1));
|
|
};
|
|
}
|
|
}
|
|
// Iterate through all definitions in src now and call field() for each.
|
|
if (src) {
|
|
beans = [];
|
|
for (var name in src)
|
|
if (src.hasOwnProperty(name) && !hidden.test(name))
|
|
field(name, null, true, generics);
|
|
// IE (and some other browsers?) never enumerate these, even if
|
|
// they are simply set on an object. Force their creation. Do not
|
|
// create generics for these, and check them for not being defined
|
|
// (by passing undefined for dontCheck).
|
|
field('toString');
|
|
field('valueOf');
|
|
// Now finally define beans as well. Look up methods on dest, for
|
|
// support of this.base() (See above).
|
|
for (var i = 0, l = beans && beans.length; i < l; i++)
|
|
try {
|
|
var bean = beans[i],
|
|
part = bean[1];
|
|
field(bean[0], {
|
|
get: dest['get' + part] || dest['is' + part],
|
|
set: dest['set' + part]
|
|
}, true);
|
|
} catch (e) {}
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
function each(obj, iter, bind, asArray) {
|
|
try {
|
|
if (obj)
|
|
(asArray || typeof asArray === 'undefined' && isArray(obj)
|
|
? forEach : forIn).call(obj, iter, bind = bind || obj);
|
|
} catch (e) {
|
|
if (e !== Base.stop)
|
|
throw e;
|
|
}
|
|
return bind;
|
|
}
|
|
|
|
function clone(obj) {
|
|
return each(obj, function(val, i) {
|
|
this[i] = val;
|
|
}, new obj.constructor());
|
|
}
|
|
|
|
// Inject into new ctor object that's passed to inject(), and then returned
|
|
// as the Base class.
|
|
return inject(function Base() {}, {
|
|
inject: function(src/*, ... */) {
|
|
if (src) {
|
|
var proto = this.prototype,
|
|
base = Object.getPrototypeOf(proto).constructor,
|
|
// Allow the whole scope to just define statics by defining
|
|
// statics: true.
|
|
statics = src.statics === true ? src : src.statics;
|
|
if (statics != src)
|
|
inject(proto, src, src.enumerable, base && base.prototype,
|
|
src.preserve, src.generics && this);
|
|
// Define new static fields as enumerable, and inherit from
|
|
// base. enumerable is necessary so they can be copied over from
|
|
// base, and it does not harm to have enumerable properties in
|
|
// the constructor. Use the preserve setting in src.preserve for
|
|
// statics too, not their own.
|
|
inject(this, statics, true, base, src.preserve);
|
|
}
|
|
// If there are more than one argument, loop through them and call
|
|
// inject again. Do not simple inline the above code in one loop,
|
|
// since each of the passed objects might override this.inject.
|
|
for (var i = 1, l = arguments.length; i < l; i++)
|
|
this.inject(arguments[i]);
|
|
return this;
|
|
},
|
|
|
|
extend: function(/* src, ... */) {
|
|
var base = this,
|
|
ctor;
|
|
// Look for an initialize function in all injection objects and use
|
|
// it directly as the actual constructor.
|
|
for (var i = 0, l = arguments.length; i < l; i++)
|
|
if (ctor = arguments[i].initialize)
|
|
break;
|
|
// If no initialize function is provided, create a constructor that
|
|
// simply calls the base constructor.
|
|
ctor = ctor || function() {
|
|
base.apply(this, arguments);
|
|
};
|
|
ctor.prototype = create(this.prototype);
|
|
// The new prototype extends the constructor on which extend is
|
|
// called. Fix constructor.
|
|
define(ctor.prototype, 'constructor',
|
|
{ value: ctor, writable: true, configurable: true });
|
|
// Copy over static fields, as prototype-like inheritance
|
|
// is not possible for static fields. Mark them as enumerable
|
|
// so they can be copied over again.
|
|
inject(ctor, this, true);
|
|
// Inject all the definitions in src. Use the new inject instead of
|
|
// the one in ctor, in case it was overriden. this is needed when
|
|
// overriding the static .inject(). But only inject if there's
|
|
// something to actually inject.
|
|
return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
|
|
}
|
|
// Pass true for enumerable, so inject() and extend() can be passed on
|
|
// to subclasses of Base through Base.inject() / extend().
|
|
}, true).inject({
|
|
/**
|
|
* Injects the fields from the given object, adding this.base()
|
|
* functionality
|
|
*/
|
|
inject: function(/* src, ... */) {
|
|
for (var i = 0, l = arguments.length; i < l; i++)
|
|
inject(this, arguments[i], arguments[i].enumerable);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns a new object that inherits all properties from "this",
|
|
* through proper JS inheritance, not copying.
|
|
* Optionally, src and hide parameters can be passed to fill in the
|
|
* newly created object just like in inject(), to copy the behavior
|
|
* of Function.prototype.extend.
|
|
*/
|
|
extend: function(/* src, ... */) {
|
|
var res = create(this);
|
|
return res.inject.apply(res, arguments);
|
|
},
|
|
|
|
each: function(iter, bind) {
|
|
return each(this, iter, bind);
|
|
},
|
|
|
|
/**
|
|
* Creates a new object of the same type and copies over all
|
|
* name / value pairs from this object.
|
|
*/
|
|
clone: function() {
|
|
return clone(this);
|
|
},
|
|
|
|
statics: {
|
|
// Expose some local privates as Base generics.
|
|
each: each,
|
|
clone: clone,
|
|
define: define,
|
|
describe: describe,
|
|
|
|
// Base.create does something different from Object.create:
|
|
// It works on constructors and uses their prototype.
|
|
create: function(ctor) {
|
|
return create(ctor.prototype);
|
|
},
|
|
|
|
/**
|
|
* Returns true if obj is a plain JavaScript object literal, or a
|
|
* plain Base object, as produced by Base.merge().
|
|
*/
|
|
isPlainObject: function(obj) {
|
|
var ctor = obj != null && obj.constructor;
|
|
// We also need to check for ctor.name === 'Object', in case
|
|
// this is an object from another global scope (e.g. an iframe,
|
|
// or another vm context in node.js).
|
|
return ctor && (ctor === Object || ctor === Base
|
|
|| ctor.name === 'Object');
|
|
},
|
|
|
|
check: function(obj) {
|
|
return !!(obj || obj === 0);
|
|
},
|
|
|
|
/**
|
|
* Returns the first argument that is defined. null is counted as
|
|
* defined too, as !== undefined is used for comparisons.
|
|
*/
|
|
pick: function() {
|
|
for (var i = 0, l = arguments.length; i < l; i++)
|
|
if (arguments[i] !== undefined)
|
|
return arguments[i];
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Base.stop can be thrown by iterators passed to each()
|
|
*
|
|
* continue (Base.next) is not implemented, as the same can achieved
|
|
* by using return in the iterator.
|
|
*/
|
|
stop: {}
|
|
}
|
|
});
|
|
};
|