steal('can/util/string', function (can) {
steal-clean
steal('can/util/string', function (can) {
can.Construct
This is a modified version of
John Resig’s class.
It provides class level inheritance and callbacks.
A private flag used to initialize a new class instance without
initializing it’s bindings.
var initializing = 0;
var canGetDescriptor;
try {
Object.getOwnPropertyDescriptor({});
canGetDescriptor = true;
} catch(e) {
canGetDescriptor = false;
}
var getDescriptor = function(newProps, name) {
var descriptor = Object.getOwnPropertyDescriptor(newProps, name);
if(descriptor && (descriptor.get || descriptor.set)) {
return descriptor;
}
return null;
},
inheritGetterSetter = function(newProps, oldProps, addTo) {
addTo = addTo || newProps;
var descriptor;
for (var name in newProps) {
if( (descriptor = getDescriptor(newProps, name)) ) {
this._defineProperty(addTo, oldProps, name, descriptor);
} else {
can.Construct._overwrite(addTo, oldProps, name, newProps[name]);
}
}
},
simpleInherit = function (newProps, oldProps, addTo) {
addTo = addTo || newProps;
for (var name in newProps) {
can.Construct._overwrite(addTo, oldProps, name, newProps[name]);
}
};
/**
* @add can.Construct
*/
can.Construct = function () {
if (arguments.length) {
return can.Construct.extend.apply(can.Construct, arguments);
}
};
/**
* @static
*/
can.extend(can.Construct, {
/**
* @property {Boolean} can.Construct.constructorExtends
* @parent can.Construct.static
*
* @description
* Toggles the behavior of a constructor function called
* without the `new` keyword to extend the constructor function or
* create a new instance.
*
* ```
* var animal = Animal();
* // vs
* var animal = new Animal();
* ```
*
* @body
*
* If `constructorExtends` is:
*
* - `true` - the constructor extends
* - `false` - a new instance of the constructor is created
*
* This property defaults to false.
*
* Example of constructExtends is true:
* ```
* var Animal = can.Construct.extend({
* constructorExtends: true // the constructor extends
* },{
* sayHi: function() {
* console.log("hai!");
* }
* });
*
* var Pony = Animal({
* gallop: function () {
* console.log("Galloping!!");
* }
* }); // Pony is now a constructor function extended from Animal
*
* var frank = new Animal(); // frank is a new instance of Animal
*
* var gertrude = new Pony(); // gertrude is a new instance of Pony
* gertrude.sayHi(); // "hai!" - sayHi is "inherited" from Animal
* gertrude.gallop(); // "Galloping!!" - gallop is unique to instances of Pony
*```
*
* The default behavior is shown in the example below:
* ```
* var Animal = can.Construct.extend({
* constructorExtends: false // the constructor does NOT extend
* },{
* sayHi: function() {
* console.log("hai!");
* }
* });
*
* var pony = Animal(); // pony is a new instance of Animal
* var frank = new Animal(); // frank is a new instance of Animal
*
* pony.sayHi() // "hai!"
* frank.sayHi() // "hai!"
*```
* By default to extend a constructor, you must use [can.Construct.extend extend].
*/
constructorExtends: true,
/**
* @function can.Construct.newInstance newInstance
* @parent can.Construct.static
*
* @description Returns an instance of `can.Construct`. This method
* can be overridden to return a cached instance.
*
* @signature `can.Construct.newInstance([...args])`
*
* @param {*} [args] arguments that get passed to [can.Construct::setup] and [can.Construct::init]. Note
* that if [can.Construct::setup] returns an array, those arguments will be passed to [can.Construct::init]
* instead.
* @return {class} instance of the class
*
* @body
* Creates a new instance of the constructor function. This method is useful for creating new instances
* with arbitrary parameters. Typically, however, you will simply want to call the constructor with the
* __new__ operator.
*
* ## Example
*
* The following creates a `Person` Construct and overrides `newInstance` to cache all
* instances of Person to prevent duplication. If the properties of a new Person match an existing one it
* will return a reference to the previously created object, otherwise it returns a new object entirely.
*
* ```
* // define and create the Person constructor
* var Person = can.Construct.extend({
* init : function(first, middle, last) {
* this.first = first;
* this.middle = middle;
* this.last = last;
* }
* });
*
* // store a reference to the original newInstance function
* var _newInstance = Person.newInstance;
*
* // override Person's newInstance function
* Person.newInstance = function() {
* // if cache does not exist make it an new object
* this.__cache = this.__cache || {};
* // id is a stingified version of the passed arguments
* var id = JSON.stringify(arguments);
*
* // look in the cache to see if the object already exists
* var cachedInst = this.__cache[id];
* if(cachedInst) {
* return cachedInst;
* }
*
* //otherwise call the original newInstance function and return a new instance of Person.
* var newInst = _newInstance.apply(this, arguments);
* this.__cache[id] = newInst;
* return newInst;
* }
*
* // create two instances with the same arguments
* var justin = new Person('Justin', 'Barry', 'Meyer'),
* brian = new Person('Justin', 'Barry', 'Meyer');
*
* console.log(justin === brian); // true - both are references to the same instance
* ```
*
*/
newInstance: function () {
Get a raw instance object (init
is not called).
var inst = this.instance(),
args;
Call setup
if there is a setup
if (inst.setup) {
inst.__inSetup = true;
args = inst.setup.apply(inst, arguments);
delete inst.__inSetup;
}
Call init
if there is an init
If setup
returned args
, use those as the arguments
if (inst.init) {
inst.init.apply(inst, args || arguments);
}
return inst;
},
Overwrites an object with methods. Used in the super
plugin.
newProps
- New properties to add.
oldProps
- Where the old properties might be (used with super
).
addTo
- What we are adding to.
_inherit: canGetDescriptor ? inheritGetterSetter : simpleInherit,
Adds a defineProperty
with the given name and descriptor
Will only ever be called if ES5 is supported
_defineProperty: function(what, oldProps, propName, descriptor) {
Object.defineProperty(what, propName, descriptor);
},
used for overwriting a single property. this should be used for patching other objects the super plugin overwrites this
_overwrite: function (what, oldProps, propName, val) {
what[propName] = val;
},
Set defaults
as the merger of the parent defaults
and this
object’s defaults
. If you overwrite this method, make sure to
include option merging logic.
/**
* @function can.Construct.setup setup
* @parent can.Construct.static
*
* @description Perform initialization logic for a constructor function.
*
* @signature `can.Construct.setup(base, fullName, staticProps, protoProps)`
*
* A static `setup` method provides inheritable setup functionality
* for a Constructor function. The following example
* creates a Group constructor function. Any constructor
* functions that inherit from Group will be added to
* `Group.childGroups`.
*
*
* Group = can.Construct.extend({
* setup: function(Construct, fullName, staticProps, protoProps){
* this.childGroups = [];
* if(Construct !== can.Construct){
* this.childGroups.push(Construct)
* }
* Construct.setup.apply(this, arguments)
* }
* },{})
* var Flock = Group.extend(...)
* Group.childGroups[0] //-> Flock
*
* @param {constructor} base The base constructor that is being inherited from.
* @param {String} fullName The name of the new constructor.
* @param {Object} staticProps The static properties of the new constructor.
* @param {Object} protoProps The prototype properties of the new constructor.
*
* @body
* The static `setup` method is called immediately after a constructor
* function is created and
* set to inherit from its base constructor. It is useful for setting up
* additional inheritance work.
* Do not confuse this with the prototype `[can.Construct::setup]` method.
*
* ## Example
*
* This `Parent` class adds a reference to its base class to itself, and
* so do all the classes that inherit from it.
*
* ```
* Parent = can.Construct.extend({
* setup : function(base, fullName, staticProps, protoProps){
* this.base = base;
*
* // call base functionality
* can.Construct.setup.apply(this, arguments)
* }
* },{});
*
* Parent.base; // can.Construct
*
* Child = Parent({});
*
* Child.base; // Parent
* ```
*/
setup: function (base, fullName) {
this.defaults = can.extend(true, {}, base.defaults, this.defaults);
},
Create’s a new class
instance without initializing by setting the
initializing
flag.
instance: function () {
Prevents running init
.
initializing = 1;
var inst = new this();
Allow running init
.
initializing = 0;
return inst;
},
Extends classes.
/**
* @function can.Construct.extend extend
* @parent can.Construct.static
*
* @signature `can.Construct.extend([name,] [staticProperties,] instanceProperties)`
*
* Extends `can.Construct`, or constructor functions derived from `can.Construct`,
* to create a new constructor function. Example:
*
* var Animal = can.Construct.extend({
* sayHi: function(){
* console.log("hi")
* }
* })
* var animal = new Animal()
* animal.sayHi();
*
* @param {String} [name] Creates the necessary properties and
* objects that point from the `window` to the created constructor function. The following:
*
* can.Construct.extend("company.project.Constructor",{})
*
* creates a `company` object on window if it does not find one, a
* `project` object on `company` if it does not find one, and it will set the
* `Constructor` property on the `project` object to point to the constructor function.
*
* Finally, it sets "company.project.Constructor" as [can.Construct.fullName fullName]
* and "Constructor" as [can.Construct.shortName shortName].
*
* @param {Object} [staticProperties] Properties that are added the constructor
* function directly. For example:
*
* ```
* var Animal = can.Construct.extend({
* findAll: function(){
* return can.ajax({url: "/animals"})
* }
* },{}); // need to pass an empty instanceProperties object
*
* Animal.findAll().then(function(json){ ... })
* ```
*
* The [can.Construct.setup static setup] method can be used to
* specify inheritable behavior when a Constructor function is created.
*
* @param {Object} instanceProperties Properties that belong to
* instances made with the constructor. These properties are added to the
* constructor's `prototype` object. Example:
*
* var Animal = can.Construct.extend({
* findAll: function() {
* return can.ajax({url: "/animals"});
* }
* },{
* init: function(name) {
* this.name = name;
* },
* sayHi: function() {
* console.log(this.name," says hai!");
* }
* })
* var pony = new Animal("Gertrude");
* pony.sayHi(); // "Gertrude says hai!"
*
* The [can.Construct::init init] and [can.Construct::setup setup] properties
* are used for initialization.
*
* @return {function} The constructor function.
* ```
* var Animal = can.Construct.extend(...);
* var pony = new Animal(); // Animal is a constructor function
* ```
* @body
* ## Inheritance
* Creating "subclasses" with `can.Construct` is simple. All you need to do is call the base constructor
* with the new function's static and instance properties. For example, we want our `Snake` to
* be an `Animal`, but there are some differences:
*
*
* var Snake = Animal.extend({
* legs: 0
* }, {
* init: function() {
* Animal.prototype.init.call(this, 'ssssss');
* },
* slither: function() {
* console.log('slithering...');
* }
* });
*
* var baslisk = new Snake();
* baslisk.speak(); // "ssssss"
* baslisk.slither(); // "slithering..."
* baslisk instanceof Snake; // true
* baslisk instanceof Animal; // true
*
*
* ## Static properties and inheritance
*
* If you pass all three arguments to can.Construct, the second one will be attached directy to the
* constructor, allowing you to imitate static properties and functions. You can access these
* properties through the `[can.Construct::constructor this.constructor]` property.
*
* Static properties can get overridden through inheritance just like instance properties. In the example below,
* we override both the legs static property as well as the the init function for each instance:
*
* ```
* var Animal = can.Construct.extend({
* legs: 4
* }, {
* init: function(sound) {
* this.sound = sound;
* },
* speak: function() {
* console.log(this.sound);
* }
* });
*
* var Snake = Animal.extend({
* legs: 0
* }, {
* init: function() {
* this.sound = 'ssssss';
* },
* slither: function() {
* console.log('slithering...');
* }
* });
*
* Animal.legs; // 4
* Snake.legs; // 0
* var dog = new Animal('woof');
* var blackMamba = new Snake();
* dog.speak(); // 'woof'
* blackMamba.speak(); // 'ssssss'
* ```
*/
extend: function (name, staticProperties, instanceProperties) {
var fullName = name,
klass = staticProperties,
proto = instanceProperties;
Figure out what was passed and normalize it.
if (typeof fullName !== 'string') {
proto = klass;
klass = fullName;
fullName = null;
}
if (!proto) {
proto = klass;
klass = null;
}
proto = proto || {};
var _super_class = this,
_super = this.prototype,
Constructor, parts, current, _fullName, _shortName, propName, shortName, namespace, prototype;
Instantiate a base class (but only create the instance, don’t run the init constructor).
prototype = this.instance();
Copy the properties over onto the new prototype.
can.Construct._inherit(proto, _super, prototype);
if(fullName) {
parts = fullName.split('.');
shortName = parts.pop();
} else if(klass && klass.shortName) {
shortName = klass.shortName;
} else if(this.shortName) {
shortName = this.shortName;
}
!steal-remove-start
/* jshint ignore:start */
In dev builds we want constructor.name to be the same as shortName. The only way to do that right now is using eval. jshint does not like this at all so we hide it
Strip semicolons
var constructorName = shortName ? shortName.replace(/;/g, '') : 'Constructor';
Assign a name to the constructor
eval('Constructor = function ' + constructorName + '() { return init.apply(this, arguments); }');
/* jshint ignore:end */
!steal-remove-end
Make sure Constructor is still defined when the constructor name code is removed.
if(typeof constructorName === 'undefined') {
Constructor = function() {
return init.apply(this, arguments);
};
}
The dummy class constructor.
function init() {
All construction is actually done in the init method.
if (!initializing) {
!steal-remove-start
if(this.constructor !== Constructor &&
We are being called without new
or we are extending.
arguments.length && Constructor.constructorExtends) {
can.dev.warn('can/construct/construct.js: extending a can.Construct without calling extend');
}
!steal-remove-end
return this.constructor !== Constructor &&
We are being called without new
or we are extending.
arguments.length && Constructor.constructorExtends ? Constructor.extend.apply(Constructor, arguments) :
We are being called with new
.
Constructor.newInstance.apply(Constructor, arguments);
}
}
Copy old stuff onto class (can probably be merged w/ inherit)
for (propName in _super_class) {
if (_super_class.hasOwnProperty(propName)) {
Constructor[propName] = _super_class[propName];
}
}
Copy new static properties on class.
can.Construct._inherit(klass, _super_class, Constructor);
Setup namespaces.
if (fullName) {
current = can.getObject(parts.join('.'), window, true);
namespace = current;
_fullName = can.underscore(fullName.replace(/\./g, "_"));
_shortName = can.underscore(shortName);
!steal-remove-start
if (current[shortName]) {
can.dev.warn("can/construct/construct.js: There's already something called " + fullName);
}
!steal-remove-end
current[shortName] = Constructor;
}
Set things that shouldn’t be overwritten.
can.extend(Constructor, {
constructor: Constructor,
prototype: prototype,
/**
* @property {String} can.Construct.namespace namespace
* @parent can.Construct.static
*
* The `namespace` property returns the namespace your constructor is in.
* This provides a way organize code and ensure globally unique types. The
* `namespace` is the [can.Construct.fullName fullName] you passed without the [can.Construct.shortName shortName].
*
* ```
* can.Construct("MyApplication.MyConstructor",{},{});
* MyApplication.MyConstructor.namespace // "MyApplication"
* MyApplication.MyConstructor.shortName // "MyConstructor"
* MyApplication.MyConstructor.fullName // "MyApplication.MyConstructor"
* ```
*/
namespace: namespace,
/**
* @property {String} can.Construct.shortName shortName
* @parent can.Construct.static
*
* If you pass a name when creating a Construct, the `shortName` property will be set to the
* name you passed without the [can.Construct.namespace namespace].
*
* ```
* can.Construct("MyApplication.MyConstructor",{},{});
* MyApplication.MyConstructor.namespace // "MyApplication"
* MyApplication.MyConstructor.shortName // "MyConstructor"
* MyApplication.MyConstructor.fullName // "MyApplication.MyConstructor"
* ```
*/
_shortName: _shortName,
/**
* @property {String} can.Construct.fullName fullName
* @parent can.Construct.static
*
* If you pass a name when creating a Construct, the `fullName` property will be set to
* the name you passed. The `fullName` consists of the [can.Construct.namespace namespace] and
* the [can.Construct.shortName shortName].
*
* ```
* can.Construct("MyApplication.MyConstructor",{},{});
* MyApplication.MyConstructor.namespace // "MyApplication"
* MyApplication.MyConstructor.shortName // "MyConstructor"
* MyApplication.MyConstructor.fullName // "MyApplication.MyConstructor"
* ```
*/
fullName: fullName,
_fullName: _fullName
});
Dojo and YUI extend undefined
if (shortName !== undefined) {
Constructor.shortName = shortName;
}
Make sure our prototype looks nice.
Constructor.prototype.constructor = Constructor;
Call the class setup
and init
var t = [_super_class].concat(can.makeArray(arguments)),
args = Constructor.setup.apply(Constructor, t);
if (Constructor.init) {
Constructor.init.apply(Constructor, args || t);
}
/**
* @prototype
*/
return Constructor; //
/**
* @property {Object} can.Construct.prototype.constructor constructor
* @parent can.Construct.prototype
*
* A reference to the constructor function that created the instance. This allows you to access
* the constructor's static properties from an instance.
*
* @body
* ## Example
*
* This can.Construct has a static counter that counts how many instances have been created:
*
* ```
* var Counter = can.Construct.extend({
* count: 0
* }, {
* init: function() {
* this.constructor.count++;
* }
* });
*
* var childCounter = new Counter();
* console.log(childCounter.constructor.count); // 1
* console.log(Counter.count); // 1
* ```
*/
}
});
/**
* @function can.Construct.prototype.setup setup
* @parent can.Construct.prototype
*
* @signature `construct.setup(...args)`
*
* A setup function for the instantiation of a constructor function.
*
* @param {*} args The arguments passed to the constructor.
*
* @return {Array|undefined} If an array is returned, the array's items are passed as
* arguments to [can.Construct::init init]. The following example always makes
* sure that init is called with a jQuery wrapped element:
*
* WidgetFactory = can.Construct.extend({
* setup: function(element){
* return [$(element)]
* }
* })
*
* MyWidget = WidgetFactory.extend({
* init: function($el){
* $el.html("My Widget!!")
* }
* })
*
* Otherwise, the arguments to the
* constructor are passed to [can.Construct::init] and the return value of `setup` is discarded.
*
* @body
*
* ## Deciding between `setup` and `init`
*
*
* Usually, you should use [can.Construct::init init] to do your constructor function's initialization.
* You should, instead, use `setup` when:
*
* - there is initialization code that you want to run before the inheriting constructor's
* `init` method is called.
* - there is initialization code that should run whether or not inheriting constructors
* call their base's `init` methods.
* - you want to modify the arguments that will get passed to `init`.
*
* ## Example
*
* This code is a simplified version of the code in [can.Control]'s setup
* method. It converts the first argument to a jQuery collection and
* extends the controller's defaults with the options that were passed.
*
*
* can.Control = can.Construct.extend({
* setup: function(domElement, rawOptions) {
* // set up this.element
* this.element = $(domElement);
*
* // set up this.options
* this.options = can.extend({},
* this.constructor.defaults,
* rawOptions
* );
*
* // pass this.element and this.options to init.
* return [this.element, this.options];
* }
* });
*
*/
can.Construct.prototype.setup = function () {};
/**
* @function can.Construct.prototype.init init
* @parent can.Construct.prototype
*
* @description Called when a new instance of a can.Construct is created.
*
* @signature `construct.init(...args)`
* @param {*} args the arguments passed to the constructor (or the items of the array returned from [can.Construct::setup])
*
* @body
* If a prototype `init` method is provided, `init` is called when a new Construct is created---
* after [can.Construct::setup]. The `init` method is where the bulk of your initialization code
* should go. A common thing to do in `init` is save the arguments passed into the constructor.
*
* ## Examples
*
* First, we'll make a Person constructor that has a first and last name:
*
* ```
* var Person = can.Construct.extend({
* init: function(first, last) {
* this.first = first;
* this.last = last;
* }
* });
*
* var justin = new Person("Justin", "Meyer");
* justin.first; // "Justin"
* justin.last; // "Meyer"
* ```
*
* Then, we'll extend Person into Programmer, and add a favorite language:
*
* ```
* var Programmer = Person.extend({
* init: function(first, last, language) {
* // call base's init
* Person.prototype.init.apply(this, arguments);
*
* // other initialization code
* this.language = language;
* },
* bio: function() {
* return "Hi! I'm " + this.first + " " + this.last +
* " and I write " + this.language + ".";
* }
* });
*
* var brian = new Programmer("Brian", "Moschel", 'ECMAScript');
* brian.bio(); // "Hi! I'm Brian Moschel and I write ECMAScript.";
* ```
*
* ## Modified Arguments
*
* [can.Construct::setup] is able to modify the arguments passed to `init`.
* If you aren't receiving the arguments you passed to `new Construct(args)`,
* check that they aren't being changed by `setup` along
* the inheritance chain.
*/
can.Construct.prototype.init = function () {};
return can.Construct;
});