ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
471 lines (438 loc) • 15.5 kB
JavaScript
//* @public
/**
Creates a JavaScript constructor function with a prototype defined by
_inProps_. __All constructors must have a unique name__.
_enyo.kind()_ makes it easy to build a constructor-with-prototype (like a
class) that has advanced features like prototype-chaining (inheritance).
A plug-in system is included for extending the abilities of the kind
generator, and constructors are allowed to perform custom operations when
subclassed.
If you make changes to _enyo.kind()_, be sure to add or update the appropriate
[unit tests](https://github.com/enyojs/enyo/tree/master/tools/test/core/tests).
For more information, see the documentation on [Creating
Kinds](key-concepts/creating-kinds.html) in the Enyo Developer Guide.
*/
enyo.kind = function(inProps) {
var name = inProps.name || "";
// cannot defer unnamed kinds, kinds with static sections, or ones with
// noDefer flag set
if (!enyo.options.noDefer && name && !inProps.noDefer) {
// make a deferred constructor to avoid a lot of kind
// processing if we're never used
var DeferredCtor = function() {
var FinalCtor;
// check for cached final constructor first, used mainly when
// developers directly use kind names in their components instead of
// strings that are resolved at runtime.
if (DeferredCtor._FinalCtor) {
FinalCtor = DeferredCtor._FinalCtor;
} else {
if (!(this instanceof DeferredCtor)) {
throw "enyo.kind: constructor called directly, not using 'new'";
}
FinalCtor = DeferredCtor._finishKindCreation();
}
var obj = enyo.delegate(FinalCtor.prototype);
var retVal = FinalCtor.apply(obj, arguments);
return retVal? retVal: obj;
};
DeferredCtor._finishKindCreation = function() {
DeferredCtor._finishKindCreation = undefined;
enyo.setPath(name, undefined);
var FinalCtor = enyo.kind.finish(inProps);
DeferredCtor._FinalCtor = FinalCtor;
inProps = null;
return FinalCtor;
};
// copy public statics into DeferredCtor; note, this means
// public static items will need to be read-only since the
// deferrred kind constructor will have a different copy of
// non-object values than the final kind constructor
if (inProps.statics) {
enyo.mixin(DeferredCtor, inProps.statics);
}
// always add the the extend capability for kinds even if they are
// deferred
DeferredCtor.extend = enyo.kind.statics.extend;
// if extend is called on a deferred constructor, it needs to know that
// so it can resolve it at that time
DeferredCtor._deferred = true;
if ((name && !enyo.getPath(name)) || enyo.kind.allowOverride) {
enyo.setPath(name, DeferredCtor);
}
else if (name) {
enyo.error("enyo.kind: " + name + " is already in use by another " +
"kind, all kind definitions must have unique names.");
}
return DeferredCtor;
} else {
// create anonymous kinds immediately
return enyo.kind.finish(inProps);
}
};
//* @protected
enyo.kind.finish = function(inProps) {
// kind-name to constructor map could be faulty now that a new kind exists, so we simply destroy the memoizations
enyo._kindCtors = {};
// extract 'name' property
var name = inProps.name || "";
delete inProps.name;
// extract 'kind' property
var hasKind = ("kind" in inProps);
var kind = inProps.kind;
delete inProps.kind;
// establish base class reference
var base = enyo.constructorForKind(kind);
var isa = base && base.prototype || null;
// if we have an explicit kind property with value undefined, we probably
// tried to reference a kind that is not yet in scope
if (hasKind && kind === undefined || base === undefined) {
var problem = kind === undefined ? 'undefined kind' : 'unknown kind (' + kind + ')';
throw "enyo.kind: Attempt to subclass an " + problem + ". Check dependencies for [" + (name || "<unnamed>") + "].";
}
// make a boilerplate constructor
var ctor = enyo.kind.makeCtor();
// semi-reserved word 'constructor' causes problems with Prototype and IE, so we rename it here
if (inProps.hasOwnProperty("constructor")) {
inProps._constructor = inProps.constructor;
delete inProps.constructor;
}
// create our prototype
//ctor.prototype = isa ? enyo.delegate(isa) : {};
enyo.setPrototype(ctor, isa ? enyo.delegate(isa) : {});
// there are special cases where a base class has a property
// that may need to be concatenated with a subclasses implementation
// as opposed to completely overwriting it...
enyo.concatHandler(ctor, inProps);
// put in our props
enyo.mixin(ctor.prototype, inProps);
// alias class name as 'kind' in the prototype
// but we actually only need to set this if a new name was used,
// not if it is inheriting from a kind anonymously
if (name) {
ctor.prototype.kindName = name;
}
// this is for anonymous constructors
else {
ctor.prototype.kindName = base && base.prototype? base.prototype.kindName: "";
}
// cache superclass constructor
ctor.prototype.base = base;
// reference our real constructor
ctor.prototype.ctor = ctor;
// support pluggable 'features'
enyo.forEach(enyo.kind.features, function(fn){ fn(ctor, inProps); });
// put reference into namespace
if ((name && !enyo.getPath(name)) || enyo.kind.allowOverride) {
enyo.setPath(name, ctor);
}
else if (name) {
enyo.error("enyo.kind: " + name + " is already in use by another " +
"kind, all kind definitions must have unique names.");
}
return ctor;
};
//* @public
/**
Creates a singleton of a given kind with a given definition.
__The name property will be the instance name of the singleton
and must be unique__.
enyo.singleton({
kind: "enyo.Control",
name: "app.MySingleton",
published: {
value: "foo"
},
makeSomething: function() {
//...
}
});
app.MySingleton.makeSomething();
app.MySingleton.setValue("bar");
*/
enyo.singleton = function(conf, context) {
// extract 'name' property (the name of our singleton)
var name = conf.name;
delete(conf.name);
// create an unnamed kind and save its constructor's function
var Kind = enyo.kind(conf);
var inst;
// create the singleton with the previous name and constructor
enyo.setPath.call(context || enyo.global, name, (inst = new Kind()));
return inst;
};
//* @protected
enyo.kind.makeCtor = function() {
var enyoConstructor = function() {
if (!(this instanceof enyoConstructor)) {
throw "enyo.kind: constructor called directly, not using 'new'";
}
// two-pass instantiation
var result;
if (this._constructor) {
// pure construction
result = this._constructor.apply(this, arguments);
}
// defer initialization until entire constructor chain has finished
if (this.constructed) {
// post-constructor initialization
this.constructed.apply(this, arguments);
}
if (result) {
return result;
}
};
return enyoConstructor;
};
// classes referenced by name may omit this namespace (e.g., "Button" instead of "enyo.Button")
enyo.kind.defaultNamespace = "enyo";
//
// feature hooks for the oop system
//
enyo.kind.features = [];
//*@protected
/**
Used internally by several mechanisms to allow safe and normalized handling
for extending a kind's super-methods. It can take a constructor, a prototype,
or an instance.
*/
enyo.kind.extendMethods = function(ctor, props, add) {
var proto = ctor.prototype || ctor,
b = proto.base;
if (!proto.inherited && b) {
proto.inherited = enyo.kind.inherited;
}
// rename constructor to _constructor to work around IE8/Prototype problems
if (props.hasOwnProperty("constructor")) {
props._constructor = props.constructor;
delete props.constructor;
}
// decorate function properties to support inherited (do this ex post facto so that
// ctor.prototype is known, relies on elements in props being copied by reference)
for (var n in props) {
var p = props[n];
if (enyo.isInherited(p)) {
// ensure that if there isn't actually a super method to call, it won't
// fail miserably - while this shouldn't happen often, it is a sanity
// check for mixin-extensions for kinds
if (add) {
p = proto[n] = p.fn(proto[n] || enyo.nop);
} else {
p = proto[n] = p.fn(b? (b.prototype[n] || enyo.nop): enyo.nop);
}
}
if (enyo.isFunction(p)) {
if (add) {
proto[n] = p;
p.displayName = n + "()";
} else {
p._inherited = b? b.prototype[n]: null;
// FIXME: we used to need some extra values for inherited, then inherited got cleaner
// but in the meantime we used these values to support logging in Object.
// For now we support this legacy situation, by suppling logging information here.
p.displayName = proto.kindName + '.' + n + '()';
}
}
}
};
enyo.kind.features.push(enyo.kind.extendMethods);
//*@protected
/**
Called by _enyo.Object_ instances attempting to
access super-methods of a parent class (kind) by calling
_this.inherited(arguments)_ from within a kind method. This
can only be done safely when there is known to be a super
class with the same method.
*/
enyo.kind.inherited = function (originals, replacements) {
// one-off methods are the fast track
var target = originals.callee;
var fn = target._inherited;
// regardless of how we got here, just ensure we actually
// have a function to call or else we throw a console
// warning to notify developers they are calling a
// super method that doesn't exist
if ("function" === typeof fn) {
var args = originals;
if (replacements) {
// combine the two arrays, with the replacements taking the first
// set of arguments, and originals filling up the rest.
args = [];
var i = 0, l = replacements.length;
for (; i < l; ++i) {
args[i] = replacements[i];
}
l = originals.length;
for (; i < l; ++i) {
args[i] = originals[i];
}
}
return fn.apply(this, args);
} else {
enyo.warn("enyo.kind.inherited: unable to find requested " +
"super-method from -> " + originals.callee.displayName + " in " + this.kindName);
}
};
// dcl inspired super-inheritance
(function (enyo) {
//* @protected
var Inherited = function (fn) {
this.fn = fn;
};
//* @public
/**
When defining a method that overrides an existing method in a kind,
you can wrap the definition in this function and it will decorate it
appropriately for inheritance to work. The _fn_ argument must be a
function that takes a single argument, usually named _sup_, and that
returns a function where _sup.apply(this, arguments)_ is used as a
mechanism to make the super-call.
The older _this.inherited(arguments)_ method still works, but this
version results in much faster code and is the only one supported for
kind mixins.
*/
enyo.inherit = function (fn) {
return new Inherited(fn);
};
//* @protected
enyo.isInherited = function (fn) {
return fn && (fn instanceof Inherited);
};
})(enyo);
//
// 'statics' feature
//
enyo.kind.features.push(function(ctor, props) {
// install common statics
if (!ctor.subclass) {
ctor.subclass = enyo.kind.statics.subclass;
}
if (!ctor.extend) {
ctor.extend = enyo.kind.statics.extend;
}
// move props statics to constructor
if (props.statics) {
enyo.mixin(ctor, props.statics);
delete ctor.prototype.statics;
}
// also support protectedStatics which won't interfere with defer
if (props.protectedStatics) {
enyo.mixin(ctor, props.protectedStatics);
delete ctor.prototype.protectedStatics;
}
// allow superclass customization
var base = ctor.prototype.base;
while (base) {
base.subclass(ctor, props);
base = base.prototype.base;
}
});
enyo.kind.statics = {
//*@public
/**
A kind may set its own _subclass()_ method as a _static.method_ for its
constructor. Whenever it is subclassed, the constructor and properties will
be passed through this method for special handling of important features.
*/
subclass: function(ctor, props) {},
//*@public
/**
This method is available on all constructors, although calling it on a
deferred constructor will force it to be resolved at that time. Call with a
hash or array of hashes to extend the current kind without creating a new
kind. Properties will override prototype properties. If a method that is
being added already exists, the new method supersedes the existing one. The
method may call _this.inherited()_ or be wrapped with _enyo.inherit_ to call
the original method (this chains multiple methods tied to a single kind). In
cases where an instance (not the class) is to be extended, it may be passed
in as the second parameter. This method does not re-run the
_enyo.kind.features_ against the constructor or instance. Returns the
constructor or the instance.
*/
extend: function(props, target) {
var ctor = this,
exts = enyo.isArray(props)? props: [props],
proto, fn;
fn = function (k, v) { return !(enyo.isFunction(v) || enyo.isInherited(v)); };
if (!target && ctor._deferred) {
ctor = enyo.checkConstructor(ctor);
}
proto = target || ctor.prototype;
for (var i=0, p; (p=exts[i]); ++i) {
enyo.concatHandler(proto, p);
enyo.kind.extendMethods(proto, p, true);
enyo.mixin(proto, p, {/*ignore: true, */filter: fn});
}
return target || ctor;
}
};
//*@protected
enyo.concatHandler = function (ctor, props) {
var p = ctor.prototype || ctor,
b = p.ctor,
k = (p === ctor);
while (b) {
if (b.concat) { b.concat(ctor, props, k); }
b = b.prototype.base;
}
};
/**
Call this with an _enyo.kind()_ constructor to make sure it's been undeferred.
*/
enyo.checkConstructor = function(inKind) {
if (enyo.isFunction(inKind)) {
// if a deferred enyo kind, finish that work first
if (inKind._FinalCtor) {
return inKind._FinalCtor;
}
if (inKind._finishKindCreation) {
return inKind._finishKindCreation();
}
}
return inKind;
};
//
// factory for kinds identified by strings
//
enyo._kindCtors = {};
enyo.constructorForKind = function(inKind) {
if (inKind === null) {
return inKind;
} else if (inKind === undefined) {
return enyo.defaultCtor;
}
else if (enyo.isFunction(inKind)) {
return enyo.checkConstructor(inKind);
}
// use memoized constructor if available...
var ctor = enyo._kindCtors[inKind];
if (ctor) {
return ctor;
}
// otherwise look it up and memoize what we find
//
// if inKind is an object in enyo, say "Control", then ctor = enyo["Control"]
// if inKind is a path under enyo, say "Heritage.Button", then ctor = enyo["Heritage.Button"] || enyo.Heritage.Button
// if inKind is a fully qualified path, say "enyo.Heritage.Button", then ctor = enyo["enyo.Heritage.Button"] || enyo.enyo.Heritage.Button || enyo.Heritage.Button
//
// Note that kind "Foo" will resolve to enyo.Foo before resolving to global "Foo".
// This is important so "Image" will map to built-in Image object, instead of enyo.Image control.
ctor = enyo.Theme[inKind] || enyo[inKind] || enyo.getPath("enyo." + inKind) || window[inKind] || enyo.getPath(inKind);
// if this is a deferred kind, run the follow-up code then refetch the kind's constructor
if (ctor && ctor._finishKindCreation) {
ctor = ctor._finishKindCreation();
}
// If what we found at this namespace isn't a function, it's definitely not a kind constructor
if (!enyo.isFunction(ctor)) {
throw "[" + inKind + "] is not the name of a valid kind.";
}
enyo._kindCtors[inKind] = ctor;
return ctor;
};
//
// namespace for current theme ("enyo.Theme.Button" references the Button specialization for the current theme)
//
enyo.Theme = {};
enyo.registerTheme = function(inNamespace) {
enyo.mixin(enyo.Theme, inNamespace);
};