diff --git a/class/class.js b/class/class.js
index cd7d6360..7a1f43e1 100644
--- a/class/class.js
+++ b/class/class.js
@@ -1,785 +1 @@
-//jQuery.Class
-// This is a modified version of John Resig's class
-// http://ejohn.org/blog/simple-javascript-inheritance/
-// It provides class level inheritance and callbacks.
-//@steal-clean
-steal("jquery","jquery/lang/string",function( $ ) {
-
- // =============== HELPERS =================
-
- // if we are initializing a new class
- var initializing = false,
- makeArray = $.makeArray,
- isFunction = $.isFunction,
- isArray = $.isArray,
- extend = $.extend,
- getObject = $.String.getObject,
- concatArgs = function(arr, args){
- return arr.concat(makeArray(args));
- },
-
- // tests if we can get super in .toString()
- fnTest = /xyz/.test(function() {
- xyz;
- }) ? /\b_super\b/ : /.*/,
-
- // overwrites an object with methods, sets up _super
- // newProps - new properties
- // oldProps - where the old properties might be
- // addTo - what we are adding to
- inheritProps = function( newProps, oldProps, addTo ) {
- addTo = addTo || newProps
- for ( var name in newProps ) {
- // Check if we're overwriting an existing function
- addTo[name] = isFunction(newProps[name]) &&
- isFunction(oldProps[name]) &&
- fnTest.test(newProps[name]) ? (function( name, fn ) {
- return function() {
- var tmp = this._super,
- ret;
-
- // Add a new ._super() method that is the same method
- // but on the super-class
- this._super = oldProps[name];
-
- // The method only need to be bound temporarily, so we
- // remove it when we're done executing
- ret = fn.apply(this, arguments);
- this._super = tmp;
- return ret;
- };
- })(name, newProps[name]) : newProps[name];
- }
- },
- STR_PROTOTYPE = 'prototype'
-
- /**
- * @class jQuery.Class
- * @plugin jquery/class
- * @parent jquerymx
- * @download dist/jquery/jquery.class.js
- * @test jquery/class/qunit.html
- * @description Easy inheritance in JavaScript.
- *
- * Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between
- * jQuery's functional programming style and Object Oriented Programming. It
- * is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class]
- * Inheritance library. Besides prototypal inheritance, it includes a few important features:
- *
- * - Static inheritance
- * - Introspection
- * - Namespaces
- * - Setup and initialization methods
- * - Easy callback function creation
- *
- *
- * The [mvc.class Get Started with jQueryMX] has a good walkthrough of $.Class.
- *
- * ## Static v. Prototype
- *
- * Before learning about Class, it's important to
- * understand the difference between
- * a class's __static__ and __prototype__ properties.
- *
- * //STATIC
- * MyClass.staticProperty //shared property
- *
- * //PROTOTYPE
- * myclass = new MyClass()
- * myclass.prototypeMethod() //instance method
- *
- * A static (or class) property is on the Class constructor
- * function itself
- * and can be thought of being shared by all instances of the
- * Class. Prototype propertes are available only on instances of the Class.
- *
- * ## A Basic Class
- *
- * The following creates a Monster class with a
- * name (for introspection), static, and prototype members.
- * Every time a monster instance is created, the static
- * count is incremented.
- *
- * @codestart
- * $.Class('Monster',
- * /* @static *|
- * {
- * count: 0
- * },
- * /* @prototype *|
- * {
- * init: function( name ) {
- *
- * // saves name on the monster instance
- * this.name = name;
- *
- * // sets the health
- * this.health = 10;
- *
- * // increments count
- * this.constructor.count++;
- * },
- * eat: function( smallChildren ){
- * this.health += smallChildren;
- * },
- * fight: function() {
- * this.health -= 2;
- * }
- * });
- *
- * hydra = new Monster('hydra');
- *
- * dragon = new Monster('dragon');
- *
- * hydra.name // -> hydra
- * Monster.count // -> 2
- * Monster.shortName // -> 'Monster'
- *
- * hydra.eat(2); // health = 12
- *
- * dragon.fight(); // health = 8
- *
- * @codeend
- *
- *
- * Notice that the prototype init function is called when a new instance of Monster is created.
- *
- *
- * ## Inheritance
- *
- * When a class is extended, all static and prototype properties are available on the new class.
- * If you overwrite a function, you can call the base class's function by calling
- * this._super. Lets create a SeaMonster class. SeaMonsters are less
- * efficient at eating small children, but more powerful fighters.
- *
- *
- * Monster("SeaMonster",{
- * eat: function( smallChildren ) {
- * this._super(smallChildren / 2);
- * },
- * fight: function() {
- * this.health -= 1;
- * }
- * });
- *
- * lockNess = new SeaMonster('Lock Ness');
- * lockNess.eat(4); //health = 12
- * lockNess.fight(); //health = 11
- *
- * ### Static property inheritance
- *
- * You can also inherit static properties in the same way:
- *
- * $.Class("First",
- * {
- * staticMethod: function() { return 1;}
- * },{})
- *
- * First("Second",{
- * staticMethod: function() { return this._super()+1;}
- * },{})
- *
- * Second.staticMethod() // -> 2
- *
- * ## Namespaces
- *
- * Namespaces are a good idea! We encourage you to namespace all of your code.
- * It makes it possible to drop your code into another app without problems.
- * Making a namespaced class is easy:
- *
- *
- * $.Class("MyNamespace.MyClass",{},{});
- *
- * new MyNamespace.MyClass()
- *
- *
- *
- * Class provides static and prototype initialization functions. - * These come in two flavors - setup and init. - * Setup is called before init and - * can be used to 'normalize' init's arguments. - *
- *init method
- * gets called in the following example:
- *
- * $.Class("Foo", {
- * init: function( arg1, arg2, arg3 ) {
- * this.sum = arg1+arg2+arg3;
- * }
- * })
- * var foo = new Foo(1,2,3);
- * foo.sum //-> 6
- *
- * ## Proxies
- *
- * Similar to jQuery's proxy method, Class provides a
- * [jQuery.Class.static.proxy proxy]
- * function that returns a callback to a method that will always
- * have
- * this set to the class or instance of the class.
- *
- *
- * The following example uses this.proxy to make sure
- * this.name is available in show.
- *
- * $.Class("Todo",{
- * init: function( name ) {
- * this.name = name
- * },
- * get: function() {
- * $.get("/stuff",this.proxy('show'))
- * },
- * show: function( txt ) {
- * alert(this.name+txt)
- * }
- * })
- * new Todo("Trash").get()
- *
- * Callback is available as a static and prototype method.
- *
- * ## Demo
- *
- * @demo jquery/class/class.html
- *
- *
- * @constructor
- *
- * To create a Class call:
- *
- * $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class
- *
- * {optional:String}
- * If provided, this sets the shortName and fullName of the - * class and adds it and any necessary namespaces to the - * window object.
- *{optional:Object}
- * If provided, this creates static properties and methods - * on the class.
- *{Object}
- * Creates prototype methods on the class.
- *.extend. Instead, you can pass those options directly to
- * $.Class (and any inheriting classes):
- *
- * // with className, static and prototype functions
- * $.Class('Task',{ STATIC },{ PROTOTYPE })
- * // with just classname and prototype functions
- * $.Class('Task',{ PROTOTYPE })
- * // with just a className
- * $.Class('Task')
- *
- * @param {String} [fullName] the classes name (used for classes w/ introspection)
- * @param {Object} [klass] the new classes static/class functions
- * @param {Object} [proto] the new classes prototype functions
- *
- * @return {jQuery.Class} returns the new class
- */
- extend: function( fullName, klass, proto ) {
- // 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[STR_PROTOTYPE],
- name, shortName, namespace, prototype;
-
- // Instantiate a base class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- prototype = new this();
- initializing = false;
-
- // Copy the properties over onto the new prototype
- inheritProps(proto, _super, prototype);
-
- // The dummy class constructor
- function Class() {
- // All construction is actually done in the init method
- if ( initializing ) return;
-
- // we are being called w/o new, we are extending
- if ( this.constructor !== Class && arguments.length ) {
- return arguments.callee.extend.apply(arguments.callee, arguments)
- } else { //we are being called w/ new
- return this.Class.newInstance.apply(this.Class, arguments)
- }
- }
- // Copy old stuff onto class
- for ( name in this ) {
- if ( this.hasOwnProperty(name) ) {
- Class[name] = this[name];
- }
- }
-
- // copy new static props on class
- inheritProps(klass, this, Class);
-
- // do namespace stuff
- if ( fullName ) {
-
- var parts = fullName.split(/\./),
- shortName = parts.pop(),
- current = getObject(parts.join('.'), window, true),
- namespace = current;
-
- //@steal-remove-start
- if (!Class.nameOk ) {
- //steal.dev.isHappyName(fullName)
- }
- if(current[shortName]){
- steal.dev.warn("class.js There's already something called "+fullName)
- }
- //@steal-remove-end
- current[shortName] = Class;
- }
-
- // set things that can't be overwritten
- extend(Class, {
- prototype: prototype,
- /**
- * @attribute namespace
- * The namespaces object
- *
- * $.Class("MyOrg.MyClass",{},{})
- * MyOrg.MyClass.namespace //-> MyOrg
- *
- */
- namespace: namespace,
- /**
- * @attribute shortName
- * The name of the class without its namespace, provided for introspection purposes.
- *
- * $.Class("MyOrg.MyClass",{},{})
- * MyOrg.MyClass.shortName //-> 'MyClass'
- * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
- *
- */
- shortName: shortName,
- constructor: Class,
- /**
- * @attribute fullName
- * The full name of the class, including namespace, provided for introspection purposes.
- *
- * $.Class("MyOrg.MyClass",{},{})
- * MyOrg.MyClass.shortName //-> 'MyClass'
- * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
- *
- */
- fullName: fullName
- });
-
- //make sure our prototype looks nice
- Class[STR_PROTOTYPE].Class = Class[STR_PROTOTYPE].constructor = Class;
-
-
-
- // call the class setup
- var args = Class.setup.apply(Class, concatArgs([_super_class],arguments));
-
- // call the class init
- if ( Class.init ) {
- Class.init.apply(Class, args || []);
- }
-
- /* @Prototype*/
- return Class;
- /**
- * @function setup
- * If a setup method is provided, it is called when a new
- * instances is created. It gets passed the same arguments that
- * were given to the Class constructor function ( new Class( arguments ... )).
- *
- * $.Class("MyClass",
- * {
- * setup: function( val ) {
- * this.val = val;
- * }
- * })
- * var mc = new MyClass("Check Check")
- * mc.val //-> 'Check Check'
- *
- * Setup is called before [jQuery.Class.prototype.init init]. If setup
- * return an array, those arguments will be used for init.
- *
- * $.Class("jQuery.Controller",{
- * setup : function(htmlElement, rawOptions){
- * return [$(htmlElement),
- * $.extend({}, this.Class.defaults, rawOptions )]
- * }
- * })
- *
- * init method is provided, it gets called when a new instance
- * is created. Init gets called after [jQuery.Class.prototype.setup setup], typically with the
- * same arguments passed to the Class
- * constructor: ( new Class( arguments ... )).
- *
- * $.Class("MyClass",
- * {
- * init: function( val ) {
- * this.val = val;
- * }
- * })
- * var mc = new MyClass(1)
- * mc.val //-> 1
- *
- * [jQuery.Class.prototype.setup Setup] is able to modify the arguments passed to init. Read
- * about it there.
- *
- */
- //Breaks up code
- /**
- * @attribute constructor
- *
- * A reference to the Class (or constructor function). This allows you to access
- * a class's static properties from an instance.
- *
- * ### Quick Example
- *
- * // a class with a static property
- * $.Class("MyClass", {staticProperty : true}, {});
- *
- * // a new instance of myClass
- * var mc1 = new MyClass();
- *
- * // read the static property from the instance:
- * mc1.constructor.staticProperty //-> true
- *
- * Getting static properties with the constructor property, like
- * [jQuery.Class.static.fullName fullName], is very common.
- *
- */
- }
-
- })
-
-
-
-
-
- clss.callback = clss[STR_PROTOTYPE].callback = clss[STR_PROTOTYPE].
- /**
- * @function proxy
- * Returns a method that sets 'this' to the current instance. This does the same thing as
- * and is described better in [jQuery.Class.static.proxy].
- * The only difference is this proxy works
- * on a instance instead of a class.
- * @param {String|Array} fname If a string, it represents the function to be called.
- * If it is an array, it will call each function in order and pass the return value of the prior function to the
- * next function.
- * @return {Function} the callback function
- */
- proxy = clss.proxy;
-
-
-})();
\ No newline at end of file
+steal('jquery/class/proxy','jquery/class/super')
diff --git a/class/class_core.js b/class/class_core.js
new file mode 100644
index 00000000..6d8e509a
--- /dev/null
+++ b/class/class_core.js
@@ -0,0 +1,624 @@
+//jQuery.Class
+// This is a modified version of John Resig's class
+// http://ejohn.org/blog/simple-javascript-inheritance/
+// It provides class level inheritance and callbacks.
+//@steal-clean
+steal("jquery","jquery/lang/string",function( $ ) {
+
+ // =============== HELPERS =================
+
+ // if we are initializing a new class
+ var initializing = false,
+ underscore = $.String.underscore;
+
+ /**
+ * @class jQuery.Class
+ * @plugin jquery/class
+ * @parent jquerymx
+ * @download dist/jquery/jquery.class.js
+ * @test jquery/class/qunit.html
+ * @description Easy inheritance in JavaScript.
+ *
+ * Class provides simulated inheritance in JavaScript. Use $.Class to bridge the gap between
+ * jQuery's functional programming style and Object Oriented Programming. It
+ * is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class]
+ * Inheritance library. Besides prototypal inheritance, it includes a few important features:
+ *
+ * - Static inheritance
+ * - Introspection
+ * - Namespaces
+ * - Setup and initialization methods
+ * - Easy callback function creation
+ *
+ *
+ * The [mvc.class Get Started with jQueryMX] has a good walkthrough of $.Class.
+ *
+ * ## Static v. Prototype
+ *
+ * Before learning about Class, it's important to
+ * understand the difference between
+ * a class's __static__ and __prototype__ properties.
+ *
+ * //STATIC
+ * MyClass.staticProperty //shared property
+ *
+ * //PROTOTYPE
+ * myclass = new MyClass()
+ * myclass.prototypeMethod() //instance method
+ *
+ * A static (or class) property is on the Class constructor
+ * function itself
+ * and can be thought of being shared by all instances of the
+ * Class. Prototype propertes are available only on instances of the Class.
+ *
+ * ## A Basic Class
+ *
+ * The following creates a Monster class with a
+ * name (for introspection), static, and prototype members.
+ * Every time a monster instance is created, the static
+ * count is incremented.
+ *
+ * @codestart
+ * $.Class('Monster',
+ * /* @static *|
+ * {
+ * count: 0
+ * },
+ * /* @prototype *|
+ * {
+ * init: function( name ) {
+ *
+ * // saves name on the monster instance
+ * this.name = name;
+ *
+ * // sets the health
+ * this.health = 10;
+ *
+ * // increments count
+ * this.constructor.count++;
+ * },
+ * eat: function( smallChildren ){
+ * this.health += smallChildren;
+ * },
+ * fight: function() {
+ * this.health -= 2;
+ * }
+ * });
+ *
+ * hydra = new Monster('hydra');
+ *
+ * dragon = new Monster('dragon');
+ *
+ * hydra.name // -> hydra
+ * Monster.count // -> 2
+ * Monster.shortName // -> 'Monster'
+ *
+ * hydra.eat(2); // health = 12
+ *
+ * dragon.fight(); // health = 8
+ *
+ * @codeend
+ *
+ *
+ * Notice that the prototype init function is called when a new instance of Monster is created.
+ *
+ *
+ * ## Inheritance
+ *
+ * When a class is extended, all static and prototype properties are available on the new class.
+ * If you overwrite a function, you can call the base class's function by calling
+ * this._super. Lets create a SeaMonster class. SeaMonsters are less
+ * efficient at eating small children, but more powerful fighters.
+ *
+ *
+ * Monster("SeaMonster",{
+ * eat: function( smallChildren ) {
+ * this._super(smallChildren / 2);
+ * },
+ * fight: function() {
+ * this.health -= 1;
+ * }
+ * });
+ *
+ * lockNess = new SeaMonster('Lock Ness');
+ * lockNess.eat(4); //health = 12
+ * lockNess.fight(); //health = 11
+ *
+ * ### Static property inheritance
+ *
+ * You can also inherit static properties in the same way:
+ *
+ * $.Class("First",
+ * {
+ * staticMethod: function() { return 1;}
+ * },{})
+ *
+ * First("Second",{
+ * staticMethod: function() { return this._super()+1;}
+ * },{})
+ *
+ * Second.staticMethod() // -> 2
+ *
+ * ## Namespaces
+ *
+ * Namespaces are a good idea! We encourage you to namespace all of your code.
+ * It makes it possible to drop your code into another app without problems.
+ * Making a namespaced class is easy:
+ *
+ *
+ * $.Class("MyNamespace.MyClass",{},{});
+ *
+ * new MyNamespace.MyClass()
+ *
+ *
+ * + * Class provides static and prototype initialization functions. + * These come in two flavors - setup and init. + * Setup is called before init and + * can be used to 'normalize' init's arguments. + *
+ *init method
+ * gets called in the following example:
+ *
+ * $.Class("Foo", {
+ * init: function( arg1, arg2, arg3 ) {
+ * this.sum = arg1+arg2+arg3;
+ * }
+ * })
+ * var foo = new Foo(1,2,3);
+ * foo.sum //-> 6
+ *
+ * ## Proxies
+ *
+ * Similar to jQuery's proxy method, Class provides a
+ * [jQuery.Class.static.proxy proxy]
+ * function that returns a callback to a method that will always
+ * have
+ * this set to the class or instance of the class.
+ *
+ *
+ * The following example uses this.proxy to make sure
+ * this.name is available in show.
+ *
+ * $.Class("Todo",{
+ * init: function( name ) {
+ * this.name = name
+ * },
+ * get: function() {
+ * $.get("/stuff",this.proxy('show'))
+ * },
+ * show: function( txt ) {
+ * alert(this.name+txt)
+ * }
+ * })
+ * new Todo("Trash").get()
+ *
+ * Callback is available as a static and prototype method.
+ *
+ * ## Demo
+ *
+ * @demo jquery/class/class.html
+ *
+ *
+ * @constructor
+ *
+ * To create a Class call:
+ *
+ * $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class
+ *
+ * {optional:String}
+ * If provided, this sets the shortName and fullName of the + * class and adds it and any necessary namespaces to the + * window object.
+ *{optional:Object}
+ * If provided, this creates static properties and methods + * on the class.
+ *{Object}
+ * Creates prototype methods on the class.
+ *.extend. Instead, you can pass those options directly to
+ * $.Class (and any inheriting classes):
+ *
+ * // with className, static and prototype functions
+ * $.Class('Task',{ STATIC },{ PROTOTYPE })
+ * // with just classname and prototype functions
+ * $.Class('Task',{ PROTOTYPE })
+ * // with just a className
+ * $.Class('Task')
+ *
+ * @param {String} [fullName] the classes name (used for classes w/ introspection)
+ * @param {Object} [klass] the new classes static/class functions
+ * @param {Object} [proto] the new classes prototype functions
+ *
+ * @return {jQuery.Class} returns the new class
+ */
+ extend: function( fullName, klass, proto ) {
+ // 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,
+ name, 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
+ this._inherit(proto, _super, prototype);
+
+ // The dummy class constructor
+ function Class() {
+ // All construction is actually done in the init method
+ if ( initializing ) return;
+
+ // we are being called w/o new, we are extending
+ if ( this.constructor !== Class && arguments.length ) {
+ return arguments.callee.extend.apply(arguments.callee, arguments)
+ } else { //we are being called w/ new
+ return this.Class.newInstance.apply(this.Class, arguments)
+ }
+ }
+ // Copy old stuff onto class (can probably be merged w/ inherit)
+ for ( name in this ) {
+ if ( this.hasOwnProperty(name) ) {
+ Class[name] = this[name];
+ }
+ }
+ // copy new static props on class
+ this._inherit(klass, this, Class);
+
+ // do namespace stuff
+ if ( fullName ) {
+
+ var parts = fullName.split(/\./),
+ shortName = parts.pop(),
+ current = $.String.getObject(parts.join('.'), window, true),
+ namespace = current,
+ _fullName = underscore(fullName.replace(/\./g, "_")),
+ _shortName = underscore(shortName);
+
+ //@steal-remove-start
+ if(current[shortName]){
+ steal.dev.warn("class.js There's already something called "+fullName)
+ }
+ //@steal-remove-end
+ current[shortName] = Class;
+ }
+
+ // set things that can't be overwritten
+ $.extend(Class, {
+ prototype: prototype,
+ /**
+ * @attribute namespace
+ * The namespaces object
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.namespace //-> MyOrg
+ *
+ */
+ namespace: namespace,
+ /**
+ * @attribute shortName
+ * The name of the class without its namespace, provided for introspection purposes.
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.shortName //-> 'MyClass'
+ * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
+ *
+ */
+ shortName: shortName,
+ _shortName : _shortName,
+ _fullName: _fullName,
+ constructor: Class,
+ /**
+ * @attribute fullName
+ * The full name of the class, including namespace, provided for introspection purposes.
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.shortName //-> 'MyClass'
+ * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
+ *
+ */
+ fullName: fullName
+ });
+
+ //make sure our prototype looks nice
+ Class.prototype.Class = Class.prototype.constructor = Class;
+
+
+
+ // call the class setup
+ var args = Class.setup.apply(Class, [_super_class].concat($.makeArray(arguments)) );
+
+ // call the class init
+ if ( Class.init ) {
+ Class.init.apply(Class, args || [_super_class].concat($.makeArray(arguments)) );
+ }
+
+ /* @Prototype*/
+ return Class;
+ /**
+ * @function setup
+ * If a setup method is provided, it is called when a new
+ * instances is created. It gets passed the same arguments that
+ * were given to the Class constructor function ( new Class( arguments ... )).
+ *
+ * $.Class("MyClass",
+ * {
+ * setup: function( val ) {
+ * this.val = val;
+ * }
+ * })
+ * var mc = new MyClass("Check Check")
+ * mc.val //-> 'Check Check'
+ *
+ * Setup is called before [jQuery.Class.prototype.init init]. If setup
+ * return an array, those arguments will be used for init.
+ *
+ * $.Class("jQuery.Controller",{
+ * setup : function(htmlElement, rawOptions){
+ * return [$(htmlElement),
+ * $.extend({}, this.Class.defaults, rawOptions )]
+ * }
+ * })
+ *
+ * init method is provided, it gets called when a new instance
+ * is created. Init gets called after [jQuery.Class.prototype.setup setup], typically with the
+ * same arguments passed to the Class
+ * constructor: ( new Class( arguments ... )).
+ *
+ * $.Class("MyClass",
+ * {
+ * init: function( val ) {
+ * this.val = val;
+ * }
+ * })
+ * var mc = new MyClass(1)
+ * mc.val //-> 1
+ *
+ * [jQuery.Class.prototype.setup Setup] is able to modify the arguments passed to init. Read
+ * about it there.
+ *
+ */
+ //Breaks up code
+ /**
+ * @attribute constructor
+ *
+ * A reference to the Class (or constructor function). This allows you to access
+ * a class's static properties from an instance.
+ *
+ * ### Quick Example
+ *
+ * // a class with a static property
+ * $.Class("MyClass", {staticProperty : true}, {});
+ *
+ * // a new instance of myClass
+ * var mc1 = new MyClass();
+ *
+ * // read the static property from the instance:
+ * mc1.constructor.staticProperty //-> true
+ *
+ * Getting static properties with the constructor property, like
+ * [jQuery.Class.static.fullName fullName], is very common.
+ *
+ */
+ }
+
+ });
+
+
+
+
+
+})();
\ No newline at end of file
diff --git a/class/class_test.js b/class/class_test.js
index ec95179c..a5a40896 100644
--- a/class/class_test.js
+++ b/class/class_test.js
@@ -128,7 +128,7 @@ test("setups", function(){
});
-test("callback", function(){
+test("proxy", function(){
var curVal = 0;
$.Class.extend("Car",{
show: function( value ) {
@@ -139,16 +139,16 @@ test("callback", function(){
}
})
- var cb = Car.callback('show');
+ var cb = Car.proxy('show');
curVal = 1;
cb(1)
curVal = 2;
- var cb2 = Car.callback('show',2)
+ var cb2 = Car.proxy('show',2)
cb2();
});
-test("callback error", 1,function(){
+test("proxy error", 1,function(){
$.Class.extend("Car",{
show: function( value ) {
equals(curVal, value)
@@ -159,7 +159,7 @@ test("callback error", 1,function(){
}
})
try{
- Car.callback('huh');
+ Car.proxy('huh');
ok(false, "I should have errored")
}catch(e){
ok(true, "Error was thrown")
diff --git a/class/proxy/proxy.html b/class/proxy/proxy.html
new file mode 100644
index 00000000..67570fac
--- /dev/null
+++ b/class/proxy/proxy.html
@@ -0,0 +1,24 @@
+
+
+
+ This is a dummy page to show off your plugin
+ + + + \ No newline at end of file diff --git a/class/proxy/proxy.js b/class/proxy/proxy.js new file mode 100644 index 00000000..2971f825 --- /dev/null +++ b/class/proxy/proxy.js @@ -0,0 +1,129 @@ +steal('jquery/class/class_core.js',function($){ +var isFunction = $.isFunction, + isArray = $.isArray, + makeArray = $.makeArray, +/** + * @function proxy + * Returns a callback function for a function on this Class. + * Proxy ensures that 'this' is set appropriately. + * @codestart + * $.Class("MyClass",{ + * getData: function() { + * this.showing = null; + * $.get("data.json",this.proxy('gotData'),'json') + * }, + * gotData: function( data ) { + * this.showing = data; + * } + * },{}); + * MyClass.showData(); + * @codeend + *This is a dummy page to show off your plugin
+ + + + \ No newline at end of file diff --git a/class/super/super.js b/class/super/super.js new file mode 100644 index 00000000..688e8056 --- /dev/null +++ b/class/super/super.js @@ -0,0 +1,40 @@ +steal('jquery/class/class_core.js',function($){ + +// tests if we can get super in .toString() + var isFunction = $.isFunction, + + fnTest = /xyz/.test(function() { + xyz; + }) ? /\b_super\b/ : /.*/; + + // overwrites an object with methods, sets up _super + // newProps - new properties + // oldProps - where the old properties might be + // addTo - what we are adding to + $.Class._inherit = function( newProps, oldProps, addTo ) { + addTo = addTo || newProps + for ( var name in newProps ) { + // Check if we're overwriting an existing function + addTo[name] = isFunction(newProps[name]) && + isFunction(oldProps[name]) && + fnTest.test(newProps[name]) ? (function( name, fn ) { + return function() { + var tmp = this._super, + ret; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = oldProps[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, newProps[name]) : newProps[name]; + } + } + + +}); \ No newline at end of file diff --git a/class/super/super_test.js b/class/super/super_test.js new file mode 100644 index 00000000..e67ac773 --- /dev/null +++ b/class/super/super_test.js @@ -0,0 +1,10 @@ +steal('funcunit/qunit','./super',function(){ + +module("super"); + +test("super testing works", function(){ + ok(true,"an assert is run"); +}); + + +}); \ No newline at end of file diff --git a/controller/controller.js b/controller/controller.js index 2f1fad9f..e0aee5a5 100644 --- a/controller/controller.js +++ b/controller/controller.js @@ -1,1083 +1 @@ -steal('jquery/class', 'jquery/lang/string', 'jquery/event/destroyed', function( $ ) { - // ------- HELPER FUNCTIONS ------ - - // Binds an element, returns a function that unbinds - var bind = function( el, ev, callback ) { - var wrappedCallback, - binder = el.bind && el.unbind ? el : $(isFunction(el) ? [el] : el); - //this is for events like >click. - if ( ev.indexOf(">") === 0 ) { - ev = ev.substr(1); - wrappedCallback = function( event ) { - if ( event.target === el ) { - callback.apply(this, arguments); - } - }; - } - binder.bind(ev, wrappedCallback || callback); - // if ev name has >, change the name and bind - // in the wrapped callback, check that the element matches the actual element - return function() { - binder.unbind(ev, wrappedCallback || callback); - el = ev = callback = wrappedCallback = null; - }; - }, - makeArray = $.makeArray, - isArray = $.isArray, - isFunction = $.isFunction, - extend = $.extend, - Str = $.String, - each = $.each, - - STR_PROTOTYPE = 'prototype', - STR_CONSTRUCTOR = 'constructor', - slice = Array[STR_PROTOTYPE].slice, - - // Binds an element, returns a function that unbinds - delegate = function( el, selector, ev, callback ) { - var binder = el.delegate && el.undelegate ? el : $(isFunction(el) ? [el] : el) - binder.delegate(selector, ev, callback); - return function() { - binder.undelegate(selector, ev, callback); - binder = el = ev = callback = selector = null; - }; - }, - - // calls bind or unbind depending if there is a selector - binder = function( el, ev, callback, selector ) { - return selector ? delegate(el, selector, ev, callback) : bind(el, ev, callback); - }, - - // moves 'this' to the first argument, wraps it with jQuery if it's an element - shifter = function shifter(context, name) { - var method = typeof name == "string" ? context[name] : name; - return function() { - context.called = name; - return method.apply(context, [this.nodeName ? $(this) : this].concat( slice.call(arguments, 0) ) ); - }; - }, - // matches dots - dotsReg = /\./g, - // matches controller - controllersReg = /_?controllers?/ig, - //used to remove the controller from the name - underscoreAndRemoveController = function( className ) { - return Str.underscore(className.replace("jQuery.", "").replace(dotsReg, '_').replace(controllersReg, "")); - }, - // checks if it looks like an action - actionMatcher = /[^\w]/, - // handles parameterized action names - parameterReplacer = /\{([^\}]+)\}/g, - breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/, - basicProcessor, - data = function(el, data){ - return $.data(el, "controllers", data) - }; - /** - * @class jQuery.Controller - * @parent jquerymx - * @plugin jquery/controller - * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js - * @test jquery/controller/qunit.html - * @inherits jQuery.Class - * @description jQuery widget factory. - * - * jQuery.Controller helps create organized, memory-leak free, rapidly performing - * jQuery widgets. Its extreme flexibility allows it to serve as both - * a traditional View and a traditional Controller. - * - * This means it is used to - * create things like tabs, grids, and contextmenus as well as - * organizing them into higher-order business rules. - * - * Controllers make your code deterministic, reusable, organized and can tear themselves - * down auto-magically. Read about [http://jupiterjs.com/news/writing-the-perfect-jquery-plugin - * the theory behind controller] and - * a [http://jupiterjs.com/news/organize-jquery-widgets-with-jquery-controller walkthrough of its features] - * on Jupiter's blog. [mvc.controller Get Started with jQueryMX] also has a great walkthrough. - * - * Controller inherits from [jQuery.Class $.Class] and makes heavy use of - * [http://api.jquery.com/delegate/ event delegation]. Make sure - * you understand these concepts before using it. - * - * ## Basic Example - * - * Instead of - * - * - * $(function(){ - * $('#tabs').click(someCallbackFunction1) - * $('#tabs .tab').click(someCallbackFunction2) - * $('#tabs .delete click').click(someCallbackFunction3) - * }); - * - * do this - * - * $.Controller('Tabs',{ - * click: function() {...}, - * '.tab click' : function() {...}, - * '.delete click' : function() {...} - * }) - * $('#tabs').tabs(); - * - * - * ## Tabs Example - * - * @demo jquery/controller/controller.html - * - * ## Using Controller - * - * Controller helps you build and organize jQuery plugins. It can be used - * to build simple widgets, like a slider, or organize multiple - * widgets into something greater. - * - * To understand how to use Controller, you need to understand - * the typical lifecycle of a jQuery widget and how that maps to - * controller's functionality: - * - * ### A controller class is created. - * - * $.Controller("MyWidget", - * { - * defaults : { - * message : "Remove Me" - * } - * }, - * { - * init : function(rawEl, rawOptions){ - * this.element.append( - * "$.fn.my_widget jQuery helper function
- * that can be used to create a new controller instance on an element. Find
- * more information [jquery.controller.plugin here] about the plugin gets created
- * and the rules around its name.
- *
- * ### An instance of controller is created on an element
- *
- * $('.thing').my_widget(options) // calls new MyWidget(el, options)
- *
- * This calls new MyWidget(el, options) on
- * each '.thing' element.
- *
- * When a new [jQuery.Class Class] instance is created, it calls the class's
- * prototype setup and init methods. Controller's [jQuery.Controller.prototype.setup setup]
- * method:
- *
- * - Sets [jQuery.Controller.prototype.element this.element] and adds the controller's name to element's className.
- * - Merges passed in options with defaults object and sets it as [jQuery.Controller.prototype.options this.options]
- * - Saves a reference to the controller in $.data.
- * - [jquery.controller.listening Binds all event handler methods].
- *
- *
- * ### The controller responds to events
- *
- * Typically, Controller event handlers are automatically bound. However, there are
- * multiple ways to [jquery.controller.listening listen to events] with a controller.
- *
- * Once an event does happen, the callback function is always called with 'this'
- * referencing the controller instance. This makes it easy to use helper functions and
- * save state on the controller.
- *
- *
- * ### The widget is destroyed
- *
- * If the element is removed from the page, the
- * controller's [jQuery.Controller.prototype.destroy] method is called.
- * This is a great place to put any additional teardown functionality.
- *
- * You can also teardown a controller programatically like:
- *
- * $('.thing').my_widget('destroy');
- *
- * ## Todos Example
- *
- * Lets look at a very basic example -
- * a list of todos and a button you want to click to create a new todo.
- * Your HTML might look like:
- *
- * @codestart html
- * <div id='todos'>
- * <ol>
- * <li class="todo">Laundry</li>
- * <li class="todo">Dishes</li>
- * <li class="todo">Walk Dog</li>
- * </ol>
- * <a class="create">Create</a>
- * </div>
- * @codeend
- *
- * To add a mousover effect and create todos, your controller might look like:
- *
- * $.Controller('Todos',{
- * ".todo mouseover" : function( el, ev ) {
- * el.css("backgroundColor","red")
- * },
- * ".todo mouseout" : function( el, ev ) {
- * el.css("backgroundColor","")
- * },
- * ".create click" : function() {
- * this.find("ol").append("<li class='todo'>New Todo</li>");
- * }
- * })
- *
- * Now that you've created the controller class, you've must attach the event handlers on the '#todos' div by
- * creating [jQuery.Controller.prototype.setup|a new controller instance]. There are 2 ways of doing this.
- *
- * @codestart
- * //1. Create a new controller directly:
- * new Todos($('#todos'));
- * //2. Use jQuery function
- * $('#todos').todos();
- * @codeend
- *
- * ## Controller Initialization
- *
- * It can be extremely useful to add an init method with
- * setup functionality for your widget.
- *
- * In the following example, I create a controller that when created, will put a message as the content of the element:
- *
- * $.Controller("SpecialController",
- * {
- * init: function( el, message ) {
- * this.element.html(message)
- * }
- * })
- * $(".special").special("Hello World")
- *
- * ## Removing Controllers
- *
- * Controller removal is built into jQuery. So to remove a controller, you just have to remove its element:
- *
- * @codestart
- * $(".special_controller").remove()
- * $("#containsControllers").html("")
- * @codeend
- *
- * It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed.
- *
- * If you just want to remove controller functionality, call destroy on the controller instance:
- *
- * @codestart
- * $(".special_controller").controller().destroy()
- * @codeend
- *
- * ## Accessing Controllers
- *
- * Often you need to get a reference to a controller, there are a few ways of doing that. For the
- * following example, we assume there are 2 elements with className="special".
- *
- * @codestart
- * //creates 2 foo controllers
- * $(".special").foo()
- *
- * //creates 2 bar controllers
- * $(".special").bar()
- *
- * //gets all controllers on all elements:
- * $(".special").controllers() //-> [foo, bar, foo, bar]
- *
- * //gets only foo controllers
- * $(".special").controllers(FooController) //-> [foo, foo]
- *
- * //gets all bar controllers
- * $(".special").controllers(BarController) //-> [bar, bar]
- *
- * //gets first controller
- * $(".special").controller() //-> foo
- *
- * //gets foo controller via data
- * $(".special").data("controllers")["FooController"] //-> foo
- * @codeend
- *
- * ## Calling methods on Controllers
- *
- * Once you have a reference to an element, you can call methods on it. However, Controller has
- * a few shortcuts:
- *
- * @codestart
- * //creates foo controller
- * $(".special").foo({name: "value"})
- *
- * //calls FooController.prototype.update
- * $(".special").foo({name: "value2"})
- *
- * //calls FooController.prototype.bar
- * $(".special").foo("bar","something I want to pass")
- * @codeend
- *
- * These methods let you call one controller from another controller.
- *
- */
- $.Class("jQuery.Controller",
- /**
- * @Static
- */
- {
- /**
- * Does 2 things:
- *
- * - Creates a jQuery helper for this controller.
- * - Calculates and caches which functions listen for events.
- *
- * ### jQuery Helper Naming Examples
- *
- *
- * "TaskController" -> $().task_controller()
- * "Controllers.Task" -> $().controllers_task()
- *
- */
- setup: function() {
- // Allow contollers to inherit "defaults" from superclasses as it done in $.Class
- this._super.apply(this, arguments);
-
- // if you didn't provide a name, or are controller, don't do anything
- if (!this.shortName || this.fullName == "jQuery.Controller" ) {
- return;
- }
- // cache the underscored names
- this._fullName = underscoreAndRemoveController(this.fullName);
- this._shortName = underscoreAndRemoveController(this.shortName);
-
- var controller = this,
- /**
- * @attribute pluginName
- * Setting the pluginName property allows you
- * to change the jQuery plugin helper name from its
- * default value.
- *
- * $.Controller("Mxui.Layout.Fill",{
- * pluginName: "fillWith"
- * },{});
- *
- * $("#foo").fillWith();
- */
- pluginname = this.pluginName || this._fullName,
- funcName, forLint;
-
- // create jQuery plugin
- if (!$.fn[pluginname] ) {
- $.fn[pluginname] = function( options ) {
-
- var args = makeArray(arguments),
- //if the arg is a method on this controller
- isMethod = typeof options == "string" && isFunction(controller[STR_PROTOTYPE][options]),
- meth = args[0];
- return this.each(function() {
- //check if created
- var controllers = data(this),
- //plugin is actually the controller instance
- plugin = controllers && controllers[pluginname];
-
- if ( plugin ) {
- if ( isMethod ) {
- // call a method on the controller with the remaining args
- plugin[meth].apply(plugin, args.slice(1));
- } else {
- // call the plugin's update method
- plugin.update.apply(plugin, args);
- }
-
- } else {
- //create a new controller instance
- controller.newInstance.apply(controller, [this].concat(args));
- }
- });
- };
- }
-
- // make sure listensTo is an array
- //@steal-remove-start
- if (!isArray(this.listensTo) ) {
- throw "listensTo is not an array in " + this.fullName;
- }
- //@steal-remove-end
- // calculate and cache actions
- this.actions = {};
-
- for ( funcName in this[STR_PROTOTYPE] ) {
- if (funcName == 'constructor' || !isFunction(this[STR_PROTOTYPE][funcName]) ) {
- continue;
- }
- if ( this._isAction(funcName) ) {
- this.actions[funcName] = this._action(funcName);
- }
- }
- },
- hookup: function( el ) {
- return new this(el);
- },
-
- /**
- * @hide
- * @param {String} methodName a prototype function
- * @return {Boolean} truthy if an action or not
- */
- _isAction: function( methodName ) {
- if ( actionMatcher.test(methodName) ) {
- return true;
- } else {
- return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName];
- }
-
- },
- /**
- * @hide
- * This takes a method name and the options passed to a controller
- * and tries to return the data necessary to pass to a processor
- * (something that binds things).
- *
- * For performance reasons, this called twice. First, it is called when
- * the Controller class is created. If the methodName is templated
- * like : "{window} foo", it returns null. If it is not templated
- * it returns event binding data.
- *
- * The resulting data is added to this.actions.
- *
- * When a controller instance is created, _action is called again, but only
- * on templated actions.
- *
- * @param {Object} methodName the method that will be bound
- * @param {Object} [options] first param merged with class default options
- * @return {Object} null or the processor and pre-split parts.
- * The processor is what does the binding/subscribing.
- */
- _action: function( methodName, options ) {
- // reset the test index
- parameterReplacer.lastIndex = 0;
-
- //if we don't have options (a controller instance), we'll run this later
- if (!options && parameterReplacer.test(methodName) ) {
- return null;
- }
- // If we have options, run sub to replace templates "{}" with a value from the options
- // or the window
- var convertedName = options ? Str.sub(methodName, [options, window]) : methodName,
-
- // If a "{}" resolves to an object, convertedName will be an array
- arr = isArray(convertedName),
-
- // get the parts of the function = [convertedName, delegatePart, eventPart]
- parts = (arr ? convertedName[1] : convertedName).match(breaker),
- event = parts[2],
- processor = processors[event] || basicProcessor;
- return {
- processor: processor,
- parts: parts,
- delegate : arr ? convertedName[0] : undefined
- };
- },
- /**
- * @attribute processors
- * An object of {eventName : function} pairs that Controller uses to hook up events
- * auto-magically. A processor function looks like:
- *
- * jQuery.Controller.processors.
- * myprocessor = function( el, event, selector, cb, controller ) {
- * //el - the controller's element
- * //event - the event (myprocessor)
- * //selector - the left of the selector
- * //cb - the function to call
- * //controller - the binding controller
- * };
- *
- * This would bind anything like: "foo~3242 myprocessor".
- *
- * The processor must return a function that when called,
- * unbinds the event handler.
- *
- * Controller already has processors for the following events:
- *
- * - change
- * - click
- * - contextmenu
- * - dblclick
- * - focusin
- * - focusout
- * - keydown
- * - keyup
- * - keypress
- * - mousedown
- * - mouseenter
- * - mouseleave
- * - mousemove
- * - mouseout
- * - mouseover
- * - mouseup
- * - reset
- * - resize
- * - scroll
- * - select
- * - submit
- *
- * Listen to events on the document or window
- * with templated event handlers:
- *
- *
- * $.Controller('Sized',{
- * "{window} resize" : function(){
- * this.element.width(this.element.parent().width() / 2);
- * }
- * });
- *
- * $('.foo').sized();
- */
- processors: {},
- /**
- * @attribute listensTo
- * An array of special events this controller
- * listens too. You only need to add event names that
- * are whole words (ie have no special characters).
- *
- * $.Controller('TabPanel',{
- * listensTo : ['show']
- * },{
- * 'show' : function(){
- * this.element.show();
- * }
- * })
- *
- * $('.foo').tab_panel().trigger("show");
- *
- */
- listensTo: [],
- /**
- * @attribute defaults
- * A object of name-value pairs that act as default values for a controller's
- * [jQuery.Controller.prototype.options options].
- *
- * $.Controller("Message",
- * {
- * defaults : {
- * message : "Hello World"
- * }
- * },{
- * init : function(){
- * this.element.text(this.options.message);
- * }
- * })
- *
- * $("#el1").message(); //writes "Hello World"
- * $("#el12").message({message: "hi"}); //writes hi
- *
- * In [jQuery.Controller.prototype.setup setup] the options passed to the controller
- * are merged with defaults. This is not a deep merge.
- */
- defaults: {}
- },
- /**
- * @Prototype
- */
- {
- /**
- * Setup is where most of controller's magic happens. It does the following:
- *
- * ### 1. Sets this.element
- *
- * The first parameter passed to new Controller(el, options) is expected to be
- * an element. This gets converted to a jQuery wrapped element and set as
- * [jQuery.Controller.prototype.element this.element].
- *
- * ### 2. Adds the controller's name to the element's className.
- *
- * Controller adds it's plugin name to the element's className for easier
- * debugging. For example, if your Controller is named "Foo.Bar", it adds
- * "foo_bar" to the className.
- *
- * ### 3. Saves the controller in $.data
- *
- * A reference to the controller instance is saved in $.data. You can find
- * instances of "Foo.Bar" like:
- *
- * $("#el").data("controllers")['foo_bar'].
- *
- * ### Binds event handlers
- *
- * Setup does the event binding described in [jquery.controller.listening Listening To Events].
- *
- * @param {HTMLElement} element the element this instance operates on.
- * @param {Object} [options] option values for the controller. These get added to
- * this.options and merged with [jQuery.Controller.static.defaults defaults].
- * @return {Array} return an array if you wan to change what init is called with. By
- * default it is called with the element and options passed to the controller.
- */
- setup: function( element, options ) {
- var funcName, ready, cls = this[STR_CONSTRUCTOR];
-
- //want the raw element here
- element = element.jquery ? element[0] : element;
-
- //set element and className on element
- var pluginname = cls.pluginName || cls._fullName;
-
- //set element and className on element
- this.element = $(element).addClass(pluginname);
-
- //set in data
- (data(element) || data(element, {}))[pluginname] = this;
-
-
- /**
- * @attribute options
- *
- * Options are used to configure an controller. They are
- * the 2nd argument
- * passed to a controller (or the first argument passed to the
- * [jquery.controller.plugin controller's jQuery plugin]).
- *
- * For example:
- *
- * $.Controller('Hello')
- *
- * var h1 = new Hello($('#content1'), {message: 'World'} );
- * equal( h1.options.message , "World" )
- *
- * var h2 = $('#content2').hello({message: 'There'})
- * .controller();
- * equal( h2.options.message , "There" )
- *
- * Options are merged with [jQuery.Controller.static.defaults defaults] in
- * [jQuery.Controller.prototype.setup setup].
- *
- * For example:
- *
- * $.Controller("Tabs",
- * {
- * defaults : {
- * activeClass: "ui-active-state"
- * }
- * },
- * {
- * init : function(){
- * this.element.addClass(this.options.activeClass);
- * }
- * })
- *
- * $("#tabs1").tabs() // adds 'ui-active-state'
- * $("#tabs2").tabs({activeClass : 'active'}) // adds 'active'
- *
- * Options are typically updated by calling
- * [jQuery.Controller.prototype.update update];
- *
- */
- this.options = extend( extend(true, {}, cls.defaults), options);
-
-
-
- /**
- * @attribute called
- * String name of current function being called on controller instance. This is
- * used for picking the right view in render.
- * @hide
- */
- this.called = "init";
-
- // bind all event handlers
- this.bind();
-
- /**
- * @attribute element
- * The controller instance's delegated element. This
- * is set by [jQuery.Controller.prototype.setup setup]. It
- * is a jQuery wrapped element.
- *
- * For example, if I add MyWidget to a '#myelement' element like:
- *
- * $.Controller("MyWidget",{
- * init : function(){
- * this.element.css("color","red")
- * }
- * })
- *
- * $("#myelement").my_widget()
- *
- * MyWidget will turn #myelement's font color red.
- *
- * ## Using a different element.
- *
- * Sometimes, you want a different element to be this.element. A
- * very common example is making progressively enhanced form widgets.
- *
- * To change this.element, overwrite Controller's setup method like:
- *
- * $.Controller("Combobox",{
- * setup : function(el, options){
- * this.oldElement = $(el);
- * var newEl = $('');
- * this.oldElement.wrap(newEl);
- * this._super(newEl, options);
- * },
- * init : function(){
- * this.element //-> the div
- * },
- * ".option click" : function(){
- * // event handler bound on the div
- * },
- * destroy : function(){
- * var div = this.element; //save reference
- * this._super();
- * div.replaceWith(this.oldElement);
- * }
- * }
- */
- return this.element;
- },
- /**
- * Bind attaches event handlers that will be
- * removed when the controller is removed.
- *
- * This used to be a good way to listen to events outside the controller's
- * [jQuery.Controller.prototype.element element]. However,
- * using templated event listeners is now the prefered way of doing this.
- *
- * ### Example:
- *
- * init: function() {
- * // calls somethingClicked(el,ev)
- * this.bind('click','somethingClicked')
- *
- * // calls function when the window is clicked
- * this.bind(window, 'click', function(ev){
- * //do something
- * })
- * },
- * somethingClicked: function( el, ev ) {
- *
- * }
- *
- * @param {HTMLElement|jQuery.fn|Object} [el=this.element]
- * The element to be bound. If an eventName is provided,
- * the controller's element is used instead.
- *
- * @param {String} eventName The event to listen for.
- * @param {Function|String} func A callback function or the String name of a controller function. If a controller
- * function name is given, the controller function is called back with the bound element and event as the first
- * and second parameter. Otherwise the function is called back like a normal bind.
- * @return {Integer} The id of the binding in this._bindings
- */
- bind: function( el, eventName, func ) {
- if( el === undefined ) {
- //adds bindings
- this._bindings = [];
- //go through the cached list of actions and use the processor to bind
-
- var cls = this[STR_CONSTRUCTOR],
- bindings = this._bindings,
- actions = cls.actions,
- element = this.element;
-
- for ( funcName in actions ) {
- if ( actions.hasOwnProperty(funcName) ) {
- ready = actions[funcName] || cls._action(funcName, this.options);
- bindings.push(
- ready.processor(ready.delegate || element,
- ready.parts[2],
- ready.parts[1],
- funcName,
- this));
- }
- }
-
-
- //setup to be destroyed ... don't bind b/c we don't want to remove it
- var destroyCB = shifter(this,"destroy");
- element.bind("destroyed", destroyCB);
- bindings.push(function( el ) {
- $(el).unbind("destroyed", destroyCB);
- });
- return bindings.length;
- }
- if ( typeof el == 'string' ) {
- func = eventName;
- eventName = el;
- el = this.element;
- }
- return this._binder(el, eventName, func);
- },
- _binder: function( el, eventName, func, selector ) {
- if ( typeof func == 'string' ) {
- func = shifter(this,func);
- }
- this._bindings.push(binder(el, eventName, func, selector));
- return this._bindings.length;
- },
- _unbind : function(){
- var el = this.element[0];
- each(this._bindings, function( key, value ) {
- value(el);
- });
- //adds bindings
- this._bindings = [];
- },
- /**
- * Delegate will delegate on an elememt and will be undelegated when the controller is removed.
- * This is a good way to delegate on elements not in a controller's element._super when overwriting
- * controller's destroy event. The base destroy functionality unbinds
- * all event handlers the controller has created.
- *
- * You could call destroy manually on an element with ChangeText
- * added like:
- *
- * $("#changed").change_text("destroy");
- *
- */
- destroy: function() {
- if ( this._destroyed ) {
- throw this[STR_CONSTRUCTOR].shortName + " controller already deleted";
- }
- var self = this,
- fname = this[STR_CONSTRUCTOR].pluginName || this[STR_CONSTRUCTOR]._fullName,
- controllers;
-
- // mark as destroyed
- this._destroyed = true;
-
- // remove the className
- this.element.removeClass(fname);
-
- // unbind bindings
- this._unbind();
- // clean up
- delete this._actions;
-
- delete this.element.data("controllers")[fname];
-
- $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed
-
- this.element = null;
- },
- /**
- * Queries from the controller's element.
- * @codestart
- * ".destroy_all click" : function() {
- * this.find(".todos").remove();
- * }
- * @codeend
- * @param {String} selector selection string
- * @return {jQuery.fn} returns the matched elements
- */
- find: function( selector ) {
- return this.element.find(selector);
- },
- //tells callback to set called on this. I hate this.
- _set_called: true
- });
-
- var processors = $.Controller.processors,
-
- //------------- PROCESSSORS -----------------------------
- //processors do the binding. They return a function that
- //unbinds when called.
- //the basic processor that binds events
- basicProcessor = function( el, event, selector, methodName, controller ) {
- return binder(el, event, shifter(controller, methodName), selector);
- };
-
-
-
-
- //set common events to be processed as a basicProcessor
- each("change click contextmenu dblclick keydown keyup keypress mousedown mousemove mouseout mouseover mouseup reset resize scroll select submit focusin focusout mouseenter mouseleave".split(" "), function( i, v ) {
- processors[v] = basicProcessor;
- });
- /**
- * @add jQuery.fn
- */
-
- //used to determine if a controller instance is one of controllers
- //controllers can be strings or classes
- var i, isAControllerOf = function( instance, controllers ) {
- for ( i = 0; i < controllers.length; i++ ) {
- if ( typeof controllers[i] == 'string' ? instance[STR_CONSTRUCTOR]._shortName == controllers[i] : instance instanceof controllers[i] ) {
- return true;
- }
- }
- return false;
- };
- $.fn.extend({
- /**
- * @function controllers
- * Gets all controllers in the jQuery element.
- * @return {Array} an array of controller instances.
- */
- controllers: function() {
- var controllerNames = makeArray(arguments),
- instances = [],
- controllers, c, cname;
- //check if arguments
- this.each(function() {
-
- controllers = $.data(this, "controllers");
- for ( cname in controllers ) {
- if ( controllers.hasOwnProperty(cname) ) {
- c = controllers[cname];
- if (!controllerNames.length || isAControllerOf(c, controllerNames) ) {
- instances.push(c);
- }
- }
- }
- });
- return instances;
- },
- /**
- * @function controller
- * Gets a controller in the jQuery element. With no arguments, returns the first one found.
- * @param {Object} controller (optional) if exists, the first controller instance with this class type will be returned.
- * @return {jQuery.Controller} the first controller.
- */
- controller: function( controller ) {
- return this.controllers.apply(this, arguments)[0];
- }
- });
-
-
-});
\ No newline at end of file
+steal('jquery/class','jquery/controller/plugin')
diff --git a/controller/controller_core.js b/controller/controller_core.js
new file mode 100644
index 00000000..659d27ac
--- /dev/null
+++ b/controller/controller_core.js
@@ -0,0 +1,977 @@
+steal('jquery/class/class_core.js', 'jquery/lang/string', 'jquery/event/destroyed', function( $ ) {
+ // ------- HELPER FUNCTIONS ------
+
+ // Binds an element, returns a function that unbinds
+ var bind = function( el, ev, callback ) {
+ var binder = el.bind && el.unbind ? el : $(isFunction(el) ? [el] : el);
+
+ binder.bind(ev, callback);
+ // if ev name has >, change the name and bind
+ // in the wrapped callback, check that the element matches the actual element
+ return function() {
+ binder.unbind(ev, callback);
+ el = ev = callback = null;
+ };
+ },
+ isFunction = $.isFunction,
+ extend = $.extend,
+ each = $.each,
+ slice = [].slice,
+
+ // Binds an element, returns a function that unbinds
+ delegate = function( el, selector, ev, callback ) {
+ var binder = el.delegate && el.undelegate ? el : $(isFunction(el) ? [el] : el)
+ binder.delegate(selector, ev, callback);
+ return function() {
+ binder.undelegate(selector, ev, callback);
+ binder = el = ev = callback = selector = null;
+ };
+ },
+
+ // calls bind or unbind depending if there is a selector
+ binder = function( el, ev, callback, selector ) {
+ return selector ? delegate(el, selector, ev, callback) : bind(el, ev, callback);
+ },
+
+ // moves 'this' to the first argument, wraps it with jQuery if it's an element
+ shifter = function shifter(context, name) {
+ var method = typeof name == "string" ? context[name] : name;
+ return function() {
+ context.called = name;
+ return method.apply(context, [this.nodeName ? $(this) : this].concat( slice.call(arguments, 0) ) );
+ };
+ },
+ // matches dots
+ // checks if it looks like an action
+ actionMatcher = /[^\w]/,
+ // handles parameterized action names
+ parameterReplacer = /\{([^\}]+)\}/g,
+ breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/,
+ basicProcessor,
+ data = function(el, data){
+ return $.data(el, "controllers", data)
+ };
+ /**
+ * @class jQuery.Controller
+ * @parent jquerymx
+ * @plugin jquery/controller
+ * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js
+ * @test jquery/controller/qunit.html
+ * @inherits jQuery.Class
+ * @description jQuery widget factory.
+ *
+ * jQuery.Controller helps create organized, memory-leak free, rapidly performing
+ * jQuery widgets. Its extreme flexibility allows it to serve as both
+ * a traditional View and a traditional Controller.
+ *
+ * This means it is used to
+ * create things like tabs, grids, and contextmenus as well as
+ * organizing them into higher-order business rules.
+ *
+ * Controllers make your code deterministic, reusable, organized and can tear themselves
+ * down auto-magically. Read about [http://jupiterjs.com/news/writing-the-perfect-jquery-plugin
+ * the theory behind controller] and
+ * a [http://jupiterjs.com/news/organize-jquery-widgets-with-jquery-controller walkthrough of its features]
+ * on Jupiter's blog. [mvc.controller Get Started with jQueryMX] also has a great walkthrough.
+ *
+ * Controller inherits from [jQuery.Class $.Class] and makes heavy use of
+ * [http://api.jquery.com/delegate/ event delegation]. Make sure
+ * you understand these concepts before using it.
+ *
+ * ## Basic Example
+ *
+ * Instead of
+ *
+ *
+ * $(function(){
+ * $('#tabs').click(someCallbackFunction1)
+ * $('#tabs .tab').click(someCallbackFunction2)
+ * $('#tabs .delete click').click(someCallbackFunction3)
+ * });
+ *
+ * do this
+ *
+ * $.Controller('Tabs',{
+ * click: function() {...},
+ * '.tab click' : function() {...},
+ * '.delete click' : function() {...}
+ * })
+ * $('#tabs').tabs();
+ *
+ *
+ * ## Tabs Example
+ *
+ * @demo jquery/controller/controller.html
+ *
+ * ## Using Controller
+ *
+ * Controller helps you build and organize jQuery plugins. It can be used
+ * to build simple widgets, like a slider, or organize multiple
+ * widgets into something greater.
+ *
+ * To understand how to use Controller, you need to understand
+ * the typical lifecycle of a jQuery widget and how that maps to
+ * controller's functionality:
+ *
+ * ### A controller class is created.
+ *
+ * $.Controller("MyWidget",
+ * {
+ * defaults : {
+ * message : "Remove Me"
+ * }
+ * },
+ * {
+ * init : function(rawEl, rawOptions){
+ * this.element.append(
+ * "$.fn.my_widget jQuery helper function
+ * that can be used to create a new controller instance on an element. Find
+ * more information [jquery.controller.plugin here] about the plugin gets created
+ * and the rules around its name.
+ *
+ * ### An instance of controller is created on an element
+ *
+ * $('.thing').my_widget(options) // calls new MyWidget(el, options)
+ *
+ * This calls new MyWidget(el, options) on
+ * each '.thing' element.
+ *
+ * When a new [jQuery.Class Class] instance is created, it calls the class's
+ * prototype setup and init methods. Controller's [jQuery.Controller.prototype.setup setup]
+ * method:
+ *
+ * - Sets [jQuery.Controller.prototype.element this.element] and adds the controller's name to element's className.
+ * - Merges passed in options with defaults object and sets it as [jQuery.Controller.prototype.options this.options]
+ * - Saves a reference to the controller in $.data.
+ * - [jquery.controller.listening Binds all event handler methods].
+ *
+ *
+ * ### The controller responds to events
+ *
+ * Typically, Controller event handlers are automatically bound. However, there are
+ * multiple ways to [jquery.controller.listening listen to events] with a controller.
+ *
+ * Once an event does happen, the callback function is always called with 'this'
+ * referencing the controller instance. This makes it easy to use helper functions and
+ * save state on the controller.
+ *
+ *
+ * ### The widget is destroyed
+ *
+ * If the element is removed from the page, the
+ * controller's [jQuery.Controller.prototype.destroy] method is called.
+ * This is a great place to put any additional teardown functionality.
+ *
+ * You can also teardown a controller programatically like:
+ *
+ * $('.thing').my_widget('destroy');
+ *
+ * ## Todos Example
+ *
+ * Lets look at a very basic example -
+ * a list of todos and a button you want to click to create a new todo.
+ * Your HTML might look like:
+ *
+ * @codestart html
+ * <div id='todos'>
+ * <ol>
+ * <li class="todo">Laundry</li>
+ * <li class="todo">Dishes</li>
+ * <li class="todo">Walk Dog</li>
+ * </ol>
+ * <a class="create">Create</a>
+ * </div>
+ * @codeend
+ *
+ * To add a mousover effect and create todos, your controller might look like:
+ *
+ * $.Controller('Todos',{
+ * ".todo mouseover" : function( el, ev ) {
+ * el.css("backgroundColor","red")
+ * },
+ * ".todo mouseout" : function( el, ev ) {
+ * el.css("backgroundColor","")
+ * },
+ * ".create click" : function() {
+ * this.find("ol").append("<li class='todo'>New Todo</li>");
+ * }
+ * })
+ *
+ * Now that you've created the controller class, you've must attach the event handlers on the '#todos' div by
+ * creating [jQuery.Controller.prototype.setup|a new controller instance]. There are 2 ways of doing this.
+ *
+ * @codestart
+ * //1. Create a new controller directly:
+ * new Todos($('#todos'));
+ * //2. Use jQuery function
+ * $('#todos').todos();
+ * @codeend
+ *
+ * ## Controller Initialization
+ *
+ * It can be extremely useful to add an init method with
+ * setup functionality for your widget.
+ *
+ * In the following example, I create a controller that when created, will put a message as the content of the element:
+ *
+ * $.Controller("SpecialController",
+ * {
+ * init: function( el, message ) {
+ * this.element.html(message)
+ * }
+ * })
+ * $(".special").special("Hello World")
+ *
+ * ## Removing Controllers
+ *
+ * Controller removal is built into jQuery. So to remove a controller, you just have to remove its element:
+ *
+ * @codestart
+ * $(".special_controller").remove()
+ * $("#containsControllers").html("")
+ * @codeend
+ *
+ * It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed.
+ *
+ * If you just want to remove controller functionality, call destroy on the controller instance:
+ *
+ * @codestart
+ * $(".special_controller").controller().destroy()
+ * @codeend
+ *
+ * ## Accessing Controllers
+ *
+ * Often you need to get a reference to a controller, there are a few ways of doing that. For the
+ * following example, we assume there are 2 elements with className="special".
+ *
+ * @codestart
+ * //creates 2 foo controllers
+ * $(".special").foo()
+ *
+ * //creates 2 bar controllers
+ * $(".special").bar()
+ *
+ * //gets all controllers on all elements:
+ * $(".special").controllers() //-> [foo, bar, foo, bar]
+ *
+ * //gets only foo controllers
+ * $(".special").controllers(FooController) //-> [foo, foo]
+ *
+ * //gets all bar controllers
+ * $(".special").controllers(BarController) //-> [bar, bar]
+ *
+ * //gets first controller
+ * $(".special").controller() //-> foo
+ *
+ * //gets foo controller via data
+ * $(".special").data("controllers")["FooController"] //-> foo
+ * @codeend
+ *
+ * ## Calling methods on Controllers
+ *
+ * Once you have a reference to an element, you can call methods on it. However, Controller has
+ * a few shortcuts:
+ *
+ * @codestart
+ * //creates foo controller
+ * $(".special").foo({name: "value"})
+ *
+ * //calls FooController.prototype.update
+ * $(".special").foo({name: "value2"})
+ *
+ * //calls FooController.prototype.bar
+ * $(".special").foo("bar","something I want to pass")
+ * @codeend
+ *
+ * These methods let you call one controller from another controller.
+ *
+ */
+ $.Class("jQuery.Controller",
+ /**
+ * @Static
+ */
+ {
+ /**
+ * Does 2 things:
+ *
+ * - Creates a jQuery helper for this controller.
+ * - Calculates and caches which functions listen for events.
+ *
+ * ### jQuery Helper Naming Examples
+ *
+ *
+ * "TaskController" -> $().task_controller()
+ * "Controllers.Task" -> $().controllers_task()
+ *
+ */
+ setup: function() {
+ // Allow contollers to inherit "defaults" from superclasses as it done in $.Class
+ $.Class.setup.apply(this, arguments);
+
+ // if you didn't provide a name, or are controller, don't do anything
+ if (this === jQuery.Controller ) {
+ return;
+ }
+ // cache the underscored names
+ var controller = this,
+ /**
+ * @attribute pluginName
+ * Setting the pluginName property allows you
+ * to change the jQuery plugin helper name from its
+ * default value.
+ *
+ * $.Controller("Mxui.Layout.Fill",{
+ * pluginName: "fillWith"
+ * },{});
+ *
+ * $("#foo").fillWith();
+ */
+ pluginName = this.pluginName || this._fullName,
+ funcName;
+
+ // create jQuery plugin
+ if(pluginName !== 'j_query_controller'){
+ this.plugin(pluginName);
+ }
+
+
+ // make sure listensTo is an array
+ //@steal-remove-start
+ if (!$.isArray(this.listensTo) ) {
+ throw "listensTo is not an array in " + this.fullName;
+ }
+ //@steal-remove-end
+ // calculate and cache actions
+ this.actions = {};
+
+ for ( funcName in this.prototype ) {
+ if (funcName == 'constructor' || !isFunction(this.prototype[funcName]) ) {
+ continue;
+ }
+ if ( this._isAction(funcName) ) {
+ this.actions[funcName] = this._action(funcName);
+ }
+ }
+ },
+ /**
+ * @hide
+ * @param {String} methodName a prototype function
+ * @return {Boolean} truthy if an action or not
+ */
+ _isAction: function( methodName ) {
+ if ( actionMatcher.test(methodName) ) {
+ return true;
+ } else {
+ return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName];
+ }
+
+ },
+ plugin : function(){},
+ /**
+ * @hide
+ * This takes a method name and the options passed to a controller
+ * and tries to return the data necessary to pass to a processor
+ * (something that binds things).
+ *
+ * For performance reasons, this called twice. First, it is called when
+ * the Controller class is created. If the methodName is templated
+ * like : "{window} foo", it returns null. If it is not templated
+ * it returns event binding data.
+ *
+ * The resulting data is added to this.actions.
+ *
+ * When a controller instance is created, _action is called again, but only
+ * on templated actions.
+ *
+ * @param {Object} methodName the method that will be bound
+ * @param {Object} [options] first param merged with class default options
+ * @return {Object} null or the processor and pre-split parts.
+ * The processor is what does the binding/subscribing.
+ */
+ _action: function( methodName, options ) {
+ // reset the test index
+ parameterReplacer.lastIndex = 0;
+
+ //if we don't have options (a controller instance), we'll run this later
+ if (!options && parameterReplacer.test(methodName) ) {
+ return null;
+ }
+ // If we have options, run sub to replace templates "{}" with a value from the options
+ // or the window
+ var convertedName = options ? $.String.sub(methodName, [options, window]) : methodName,
+
+ // If a "{}" resolves to an object, convertedName will be an array
+ arr = $.isArray(convertedName),
+
+ // get the parts of the function = [convertedName, delegatePart, eventPart]
+ parts = (arr ? convertedName[1] : convertedName).match(breaker),
+ event = parts[2],
+ processor = processors[event] || basicProcessor;
+ return {
+ processor: processor,
+ parts: parts,
+ delegate : arr ? convertedName[0] : undefined
+ };
+ },
+ /**
+ * @attribute processors
+ * An object of {eventName : function} pairs that Controller uses to hook up events
+ * auto-magically. A processor function looks like:
+ *
+ * jQuery.Controller.processors.
+ * myprocessor = function( el, event, selector, cb, controller ) {
+ * //el - the controller's element
+ * //event - the event (myprocessor)
+ * //selector - the left of the selector
+ * //cb - the function to call
+ * //controller - the binding controller
+ * };
+ *
+ * This would bind anything like: "foo~3242 myprocessor".
+ *
+ * The processor must return a function that when called,
+ * unbinds the event handler.
+ *
+ * Controller already has processors for the following events:
+ *
+ * - change
+ * - click
+ * - contextmenu
+ * - dblclick
+ * - focusin
+ * - focusout
+ * - keydown
+ * - keyup
+ * - keypress
+ * - mousedown
+ * - mouseenter
+ * - mouseleave
+ * - mousemove
+ * - mouseout
+ * - mouseover
+ * - mouseup
+ * - reset
+ * - resize
+ * - scroll
+ * - select
+ * - submit
+ *
+ * Listen to events on the document or window
+ * with templated event handlers:
+ *
+ *
+ * $.Controller('Sized',{
+ * "{window} resize" : function(){
+ * this.element.width(this.element.parent().width() / 2);
+ * }
+ * });
+ *
+ * $('.foo').sized();
+ */
+ processors: {},
+ /**
+ * @attribute listensTo
+ * An array of special events this controller
+ * listens too. You only need to add event names that
+ * are whole words (ie have no special characters).
+ *
+ * $.Controller('TabPanel',{
+ * listensTo : ['show']
+ * },{
+ * 'show' : function(){
+ * this.element.show();
+ * }
+ * })
+ *
+ * $('.foo').tab_panel().trigger("show");
+ *
+ */
+ listensTo: [],
+ /**
+ * @attribute defaults
+ * A object of name-value pairs that act as default values for a controller's
+ * [jQuery.Controller.prototype.options options].
+ *
+ * $.Controller("Message",
+ * {
+ * defaults : {
+ * message : "Hello World"
+ * }
+ * },{
+ * init : function(){
+ * this.element.text(this.options.message);
+ * }
+ * })
+ *
+ * $("#el1").message(); //writes "Hello World"
+ * $("#el12").message({message: "hi"}); //writes hi
+ *
+ * In [jQuery.Controller.prototype.setup setup] the options passed to the controller
+ * are merged with defaults. This is not a deep merge.
+ */
+ defaults: {}
+ },
+ /**
+ * @Prototype
+ */
+ {
+ /**
+ * Setup is where most of controller's magic happens. It does the following:
+ *
+ * ### 1. Sets this.element
+ *
+ * The first parameter passed to new Controller(el, options) is expected to be
+ * an element. This gets converted to a jQuery wrapped element and set as
+ * [jQuery.Controller.prototype.element this.element].
+ *
+ * ### 2. Adds the controller's name to the element's className.
+ *
+ * Controller adds it's plugin name to the element's className for easier
+ * debugging. For example, if your Controller is named "Foo.Bar", it adds
+ * "foo_bar" to the className.
+ *
+ * ### 3. Saves the controller in $.data
+ *
+ * A reference to the controller instance is saved in $.data. You can find
+ * instances of "Foo.Bar" like:
+ *
+ * $("#el").data("controllers")['foo_bar'].
+ *
+ * ### Binds event handlers
+ *
+ * Setup does the event binding described in [jquery.controller.listening Listening To Events].
+ *
+ * @param {HTMLElement} element the element this instance operates on.
+ * @param {Object} [options] option values for the controller. These get added to
+ * this.options and merged with [jQuery.Controller.static.defaults defaults].
+ * @return {Array} return an array if you wan to change what init is called with. By
+ * default it is called with the element and options passed to the controller.
+ */
+ setup: function( element, options ) {
+ var funcName, ready, cls = this.constructor;
+
+ //want the raw element here
+ element = (typeof element == 'string' ? $(element) :
+ (element.jquery ? element : [element]) )[0];
+
+ //set element and className on element
+ var pluginname = cls.pluginName || cls._fullName;
+
+ this.element = $(element)
+
+ if(pluginname !== 'j_query_controller') {
+ //set element and className on element
+ this.element.addClass(pluginname);
+
+ //set in data
+ (data(element) || data(element, {}))[pluginname] = this;
+ }
+
+
+
+ /**
+ * @attribute options
+ *
+ * Options are used to configure an controller. They are
+ * the 2nd argument
+ * passed to a controller (or the first argument passed to the
+ * [jquery.controller.plugin controller's jQuery plugin]).
+ *
+ * For example:
+ *
+ * $.Controller('Hello')
+ *
+ * var h1 = new Hello($('#content1'), {message: 'World'} );
+ * equal( h1.options.message , "World" )
+ *
+ * var h2 = $('#content2').hello({message: 'There'})
+ * .controller();
+ * equal( h2.options.message , "There" )
+ *
+ * Options are merged with [jQuery.Controller.static.defaults defaults] in
+ * [jQuery.Controller.prototype.setup setup].
+ *
+ * For example:
+ *
+ * $.Controller("Tabs",
+ * {
+ * defaults : {
+ * activeClass: "ui-active-state"
+ * }
+ * },
+ * {
+ * init : function(){
+ * this.element.addClass(this.options.activeClass);
+ * }
+ * })
+ *
+ * $("#tabs1").tabs() // adds 'ui-active-state'
+ * $("#tabs2").tabs({activeClass : 'active'}) // adds 'active'
+ *
+ * Options are typically updated by calling
+ * [jQuery.Controller.prototype.update update];
+ *
+ */
+ this.options = extend( extend(true, {}, cls.defaults), options);
+
+
+
+
+ // bind all event handlers
+ this.bind();
+
+ /**
+ * @attribute element
+ * The controller instance's delegated element. This
+ * is set by [jQuery.Controller.prototype.setup setup]. It
+ * is a jQuery wrapped element.
+ *
+ * For example, if I add MyWidget to a '#myelement' element like:
+ *
+ * $.Controller("MyWidget",{
+ * init : function(){
+ * this.element.css("color","red")
+ * }
+ * })
+ *
+ * $("#myelement").my_widget()
+ *
+ * MyWidget will turn #myelement's font color red.
+ *
+ * ## Using a different element.
+ *
+ * Sometimes, you want a different element to be this.element. A
+ * very common example is making progressively enhanced form widgets.
+ *
+ * To change this.element, overwrite Controller's setup method like:
+ *
+ * $.Controller("Combobox",{
+ * setup : function(el, options){
+ * this.oldElement = $(el);
+ * var newEl = $('');
+ * this.oldElement.wrap(newEl);
+ * this._super(newEl, options);
+ * },
+ * init : function(){
+ * this.element //-> the div
+ * },
+ * ".option click" : function(){
+ * // event handler bound on the div
+ * },
+ * destroy : function(){
+ * var div = this.element; //save reference
+ * this._super();
+ * div.replaceWith(this.oldElement);
+ * }
+ * }
+ */
+ return [this.element, this.options];
+ },
+ /**
+ * Bind attaches event handlers that will be
+ * removed when the controller is removed.
+ *
+ * This used to be a good way to listen to events outside the controller's
+ * [jQuery.Controller.prototype.element element]. However,
+ * using templated event listeners is now the prefered way of doing this.
+ *
+ * ### Example:
+ *
+ * init: function() {
+ * // calls somethingClicked(el,ev)
+ * this.bind('click','somethingClicked')
+ *
+ * // calls function when the window is clicked
+ * this.bind(window, 'click', function(ev){
+ * //do something
+ * })
+ * },
+ * somethingClicked: function( el, ev ) {
+ *
+ * }
+ *
+ * @param {HTMLElement|jQuery.fn|Object} [el=this.element]
+ * The element to be bound. If an eventName is provided,
+ * the controller's element is used instead.
+ *
+ * @param {String} eventName The event to listen for.
+ * @param {Function|String} func A callback function or the String name of a controller function. If a controller
+ * function name is given, the controller function is called back with the bound element and event as the first
+ * and second parameter. Otherwise the function is called back like a normal bind.
+ * @return {Integer} The id of the binding in this._bindings
+ */
+ bind: function( el, eventName, func ) {
+ if( el === undefined ) {
+ //adds bindings
+ this._bindings = [];
+ //go through the cached list of actions and use the processor to bind
+
+ var cls = this.constructor,
+ bindings = this._bindings,
+ actions = cls.actions,
+ element = this.element;
+
+ for ( funcName in actions ) {
+ if ( actions.hasOwnProperty(funcName) ) {
+ ready = actions[funcName] || cls._action(funcName, this.options);
+ bindings.push(
+ ready.processor(ready.delegate || element,
+ ready.parts[2],
+ ready.parts[1],
+ funcName,
+ this));
+ }
+ }
+
+
+ //setup to be destroyed ... don't bind b/c we don't want to remove it
+ var destroyCB = shifter(this,"destroy");
+ element.bind("destroyed", destroyCB);
+ bindings.push(function( el ) {
+ $(el).unbind("destroyed", destroyCB);
+ });
+ return bindings.length;
+ }
+ if ( typeof el == 'string' ) {
+ func = eventName;
+ eventName = el;
+ el = this.element;
+ }
+ return this._binder(el, eventName, func);
+ },
+ _binder: function( el, eventName, func, selector ) {
+ if ( typeof func == 'string' ) {
+ func = shifter(this,func);
+ }
+ this._bindings.push(binder(el, eventName, func, selector));
+ return this._bindings.length;
+ },
+ _unbind : function(){
+ var el = this.element[0];
+ each(this._bindings, function( key, value ) {
+ value(el);
+ });
+ //adds bindings
+ this._bindings = [];
+ },
+ /**
+ * Delegate will delegate on an elememt and will be undelegated when the controller is removed.
+ * This is a good way to delegate on elements not in a controller's element._super when overwriting
+ * controller's destroy event. The base destroy functionality unbinds
+ * all event handlers the controller has created.
+ *
+ * You could call destroy manually on an element with ChangeText
+ * added like:
+ *
+ * $("#changed").change_text("destroy");
+ *
+ */
+ destroy: function() {
+ var Class= this.constructor;
+
+ var self = this,
+ pluginName = Class.pluginName || Class._fullName,
+ controllers;
+
+ // unbind bindings
+ this._unbind();
+
+ if(pluginName !== 'j_query_controller'){
+ // remove the className
+ this.element.removeClass(fname);
+
+ // remove from data
+ delete this.element.data("controllers")[fname];
+ }
+
+ $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed
+
+ this.element = null;
+ },
+ /**
+ * Queries from the controller's element.
+ * @codestart
+ * ".destroy_all click" : function() {
+ * this.find(".todos").remove();
+ * }
+ * @codeend
+ * @param {String} selector selection string
+ * @return {jQuery.fn} returns the matched elements
+ */
+ find: function( selector ) {
+ return this.element.find(selector);
+ }
+ });
+
+ var processors = $.Controller.processors,
+
+ //------------- PROCESSSORS -----------------------------
+ //processors do the binding. They return a function that
+ //unbinds when called.
+ //the basic processor that binds events
+ basicProcessor = function( el, event, selector, methodName, controller ) {
+ return binder(el, event, shifter(controller, methodName), selector);
+ };
+
+
+
+
+ //set common events to be processed as a basicProcessor
+ each("change click contextmenu dblclick keydown keyup keypress mousedown mousemove mouseout mouseover mouseup reset resize scroll select submit focusin focusout mouseenter mouseleave".split(" "), function( i, v ) {
+ processors[v] = basicProcessor;
+ });
+
+
+
+
+});
\ No newline at end of file
diff --git a/controller/controller_test.js b/controller/controller_test.js
index d94799e9..b505c7ac 100644
--- a/controller/controller_test.js
+++ b/controller/controller_test.js
@@ -222,13 +222,13 @@ test("pluginName", function() {
})
test("inherit defaults", function() {
- $.Controller.extend("BaseController", {
+ $.Controller("BaseController", {
defaults : {
foo: 'bar'
}
}, {});
- BaseController.extend("InheritingController", {
+ BaseController("InheritingController", {
defaults : {
newProp : 'newVal'
}
@@ -236,7 +236,9 @@ test("inherit defaults", function() {
ok(InheritingController.defaults.foo === 'bar', 'Class must inherit defaults from the parent class');
ok(InheritingController.defaults.newProp == 'newVal', 'Class must have own defaults');
+
var inst = new InheritingController($(''), {});
+
ok(inst.options.foo === 'bar', 'Instance must inherit defaults from the parent class');
ok(inst.options.newProp == 'newVal', 'Instance must have defaults of it`s class');
});
diff --git a/controller/plugin/plugin.js b/controller/plugin/plugin.js
new file mode 100644
index 00000000..e9df51df
--- /dev/null
+++ b/controller/plugin/plugin.js
@@ -0,0 +1,93 @@
+steal('jquery/controller/controller_core.js', function(){
+
+/**
+ * @add jQuery.fn
+ */
+
+//used to determine if a controller instance is one of controllers
+//controllers can be strings or classes
+var i,
+isAControllerOf = function( instance, controllers ) {
+ for ( i = 0; i < controllers.length; i++ ) {
+ if ( typeof controllers[i] == 'string' ? instance.constructor._shortName == controllers[i] : instance instanceof controllers[i] ) {
+ return true;
+ }
+ }
+ return false;
+},
+data = function(el, data){
+ return $.data(el, "controllers", data)
+},
+makeArray = $.makeArray;
+
+
+$.fn.extend({
+ /**
+ * @function controllers
+ * Gets all controllers in the jQuery element.
+ * @return {Array} an array of controller instances.
+ */
+ controllers: function() {
+ var controllerNames = makeArray(arguments),
+ instances = [],
+ controllers, c, cname;
+ //check if arguments
+ this.each(function() {
+
+ controllers = $.data(this, "controllers");
+ for ( cname in controllers ) {
+ if ( controllers.hasOwnProperty(cname) ) {
+ c = controllers[cname];
+ if (!controllerNames.length || isAControllerOf(c, controllerNames) ) {
+ instances.push(c);
+ }
+ }
+ }
+ });
+ return instances;
+ },
+ /**
+ * @function controller
+ * Gets a controller in the jQuery element. With no arguments, returns the first one found.
+ * @param {Object} controller (optional) if exists, the first controller instance with this class type will be returned.
+ * @return {jQuery.Controller} the first controller.
+ */
+ controller: function( controller ) {
+ return this.controllers.apply(this, arguments)[0];
+ }
+});
+
+$.Controller.plugin = function(pluginname){
+ var controller = this;
+
+ if (!$.fn[pluginname]) {
+ $.fn[pluginname] = function(options){
+
+ var args = makeArray(arguments), //if the arg is a method on this controller
+ isMethod = typeof options == "string" && $.isFunction(controller.prototype[options]), meth = args[0];
+ return this.each(function(){
+ //check if created
+ var controllers = data(this), //plugin is actually the controller instance
+ plugin = controllers && controllers[pluginname];
+
+ if (plugin) {
+ if (isMethod) {
+ // call a method on the controller with the remaining args
+ plugin[meth].apply(plugin, args.slice(1));
+ }
+ else {
+ // call the plugin's update method
+ plugin.update.apply(plugin, args);
+ }
+
+ }
+ else {
+ //create a new controller instance
+ controller.newInstance.apply(controller, [this].concat(args));
+ }
+ });
+ };
+ }
+}
+
+});
\ No newline at end of file
diff --git a/controller/route/qunit.html b/controller/route/qunit.html
new file mode 100644
index 00000000..25edc491
--- /dev/null
+++ b/controller/route/qunit.html
@@ -0,0 +1,18 @@
+
+
+
+
+ hash.
@@ -206,7 +207,7 @@ function( $ ) {
* @param {Object} [defaults] an object of default values
* @return {jQuery.route}
*/
- var $route = $.route = function( url, defaults ) {
+ $.route = function( url, defaults ) {
// Extract the variable names and replace with regEx that will match an atual URL with values.
var names = [],
test = url.replace(matcher, function( whole, name ) {
@@ -215,7 +216,7 @@ function( $ ) {
});
// Add route in a form that can be easily figured out
- $route.routes[url] = {
+ $.route.routes[url] = {
// A regular expression that will match the route when variable values
// are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which
// will match for any value of :page and :type (word chars or period).
@@ -229,10 +230,10 @@ function( $ ) {
// The number of parts in the URL separated by '/'.
length: url.split('/').length
}
- return $route;
+ return $.route;
};
- $.extend($route, {
+ extend($.route, {
/**
* Parameterizes the raw JS object representation provided in data.
* If a route matching the provided data is found that URL is built
@@ -245,20 +246,20 @@ function( $ ) {
param: function( data ) {
// Check if the provided data keys match the names in any routes;
// get the one with the most matches.
+ delete data.route;
var route,
matches = -1,
- temp,
matchCount;
- for ( var name in $route.routes ) {
- temp = $route.routes[name],
- matchCount = matchesData(temp, data);
+ each($.route.routes, function(name, temp){
+ matchCount = matchesData(temp, data);
if ( matchCount > matches ) {
route = temp;
matches = matchCount
}
- }
+ });
+
if ( route ) {
- var cpy = $.extend({}, data),
+ var cpy = extend({}, data),
// Create the url by replacing the var names with the provided data.
// If the default value is found an empty string is inserted.
res = route.route.replace(matcher, function( whole, name ) {
@@ -266,13 +267,12 @@ function( $ ) {
return data[name] === route.defaults[name] ? "" : encode( data[name] );
}),
after;
-
// remove matching default values
- for(name in route.defaults) {
- if(cpy[name] === route.defaults[name]) {
+ each(route.defaults, function(name,val){
+ if(cpy[name] === val) {
delete cpy[name]
}
- }
+ })
// The remaining elements of data are added as
// $amp; separated parameters to the url.
@@ -293,12 +293,11 @@ function( $ ) {
var route = {
length: -1
};
- for ( var name in $route.routes ) {
- var temp = $route.routes[name]
+ each($.route.routes, function(name, temp){
if ( temp.test.test(url) && temp.length > route.length ) {
route = temp;
}
- }
+ });
// If a route was matched
if ( route.length > -1 ) {
var // Since RegEx backreferences are used in route.test (round brackets)
@@ -312,13 +311,14 @@ function( $ ) {
obj = (remainder && paramsMatcher.test(remainder)) ? $.String.deparam( remainder.slice(1) ) : {};
// Add the default values for this route
- obj = $.extend(true, {}, route.defaults, obj);
+ obj = extend(true, {}, route.defaults, obj);
// Overwrite each of the default values in obj with those in parts if that part is not empty.
- for ( var p = 0; p < parts.length; p++ ) {
- if ( parts[p] ) {
- obj[route.names[p]] = decode( parts[p] );
+ each(parts,function(i, part){
+ if ( part ) {
+ obj[route.names[i]] = decode( part );
}
- }
+ });
+ obj.route = route.route;
return obj;
}
// If no route was matched it is parsed as a &key=value list.
@@ -363,7 +363,7 @@ function( $ ) {
if( val === true || onready === true ) {
setState();
}
- return $route;
+ return $.route;
},
/**
* Returns a url from the options
@@ -373,9 +373,9 @@ function( $ ) {
*/
url: function( options, merge ) {
if (merge) {
- return "#!" + $route.param($.extend({}, curParams, options))
+ return "#!" + $.route.param(extend({}, curParams, options))
} else {
- return "#!" + $route.param(options)
+ return "#!" + $.route.param(options)
}
},
/**
@@ -387,8 +387,8 @@ function( $ ) {
*/
link: function( name, options, props, merge ) {
return "" + name + "";
},
/**
@@ -397,26 +397,8 @@ function( $ ) {
* @return {Boolean}
*/
current: function( options ) {
- return location.hash == "#!" + $route.param(options)
- },
- /**
- * Change the current page using either a data object or a url string.
- * @param {Object|String} loc The object with attributes or hash string.
- * @param {Boolean} remove true to remove properties not in loc, only if loc === Object, default true.
- * @return $.route Fluent interface.
- */
- set: function(loc, remove) {
- if ($.isPlainObject( loc )) {
- $route.attrs( loc, (typeof remove == "undefined") ? true : remove );
- } else if (typeof loc == "string") {
- var pre = "";
- if (loc[0] != '!' && loc[1] != '!') {
- pre = '#!';
- }
- location.hash = pre + loc;
- }
- return $route;
- }
+ return location.hash == "#!" + $.route.param(options)
+ }
});
// onready
$(function() {
@@ -425,19 +407,19 @@ function( $ ) {
// The functions in the following list applied to $.route (e.g. $.route.attr('...')) will
// instead act on the $.route.data Observe.
- $.each(['bind','unbind','delegate','undelegate','attr','attrs','serialize','removeAttr'], function(i, name){
- $route[name] = function(){
- return $route.data[name].apply($route.data, arguments)
+ each(['bind','unbind','delegate','undelegate','attr','serialize','removeAttr'], function(i, name){
+ $.route[name] = function(){
+ return $.route.data[name].apply($.route.data, arguments)
}
})
var // A throttled function called multiple times will only fire once the
// timer runs down. Each call resets the timer.
- throttle = function( func, time ) {
+ throttle = function( func ) {
var timer;
return function() {
clearTimeout(timer);
- timer = setTimeout(func, time || 1);
+ timer = setTimeout(func, 1);
}
},
// Intermediate storage for $.route.data.
@@ -445,17 +427,11 @@ function( $ ) {
// Deparameterizes the portion of the hash of interest and assign the
// values to the $.route.data removing existing values no longer in the hash.
setState = function() {
- // commented out code handles people setting attrs before onready
- //if( $.isEmptyObject( $route.data.serialize() ) ) {
- var hash = location.hash.substr(1, 1) === '!' ?
- location.hash.slice(2) :
- location.hash.slice(1); // everything after #!
- curParams = $route.deparam( hash );
- $route.attrs(curParams, true);
- //} else {
- // window.location.hash = "#!" + $route.param($route.data.serialize())
- //}
-
+ var hash = location.hash.substr(1, 1) === '!' ?
+ location.hash.slice(2) :
+ location.hash.slice(1); // everything after #!
+ curParams = $.route.deparam( hash );
+ $.route.attr(curParams, true);
};
// If the hash changes, update the $.route.data
@@ -464,7 +440,7 @@ function( $ ) {
// If the $.route.data changes, update the hash.
// Using .serialize() retrieves the raw data contained in the observable.
// This function is throttled so it only updates once even if multiple values changed.
- $route.data.bind("change", throttle(function() {
- location.hash = "#!" + $route.param($route.data.serialize())
+ $.route.bind("change", throttle(function() {
+ location.hash = "#!" + $.route.param($.route.serialize())
}));
})
\ No newline at end of file
diff --git a/event/default/default.js b/event/default/default.js
index 4ac8100b..050512ff 100644
--- a/event/default/default.js
+++ b/event/default/default.js
@@ -1,5 +1,5 @@
-steal('jquery/event', 'jquery/event/handle').then(function($){
+steal('jquery/event').then(function($){
/**
* @function jQuery.fn.triggerAsync
@@ -20,7 +20,8 @@ steal('jquery/event', 'jquery/event/handle').then(function($){
* $('#other').addClass('error');
* });
*
- * triggerAsync is design to work with the [jquery.event.pause] plugin although it is defined in _jquery/event/default_.
+ * triggerAsync is design to work with the [jquery.event.pause]
+ * plugin although it is defined in _jquery/event/default_.
*
* @param {String} type The type of event
* @param {Object} data The data for the event
@@ -105,114 +106,13 @@ $event.special["default"] = {
//save the type
types[handleObj.namespace.replace(rnamespaces,"")] = true;
- //move the handler ...
- var origHandler = handleObj.handler;
- handleObj.origHandler = origHandler;
- handleObj.handler = function(ev, data){
- if(!ev._defaultActions) ev._defaultActions = [];
- ev._defaultActions.push({element: this, handler: origHandler, event: ev, data: data, currentTarget: ev.currentTarget})
- }
- },
- setup: function() {return true},
- triggerDefault : function(event, elem, data){
-
- var defaultGetter = jQuery.Event("default."+event.type);
-
- $.extend(defaultGetter,{
- target: elem,
- _defaultActions: event._defaultActions,
- exclusive : true
- });
-
- defaultGetter.stopPropagation();
-
- //default events only work on elements
- if(elem){
- // Event object or event type
- var type = defaultGetter.type || event, namespaces = [], exclusive;
-
- if (type.indexOf("!") >= 0) {
- // Exclusive events trigger only for the exact event (no namespaces)
- type = type.slice(0, -1);
- exclusive = true;
- }
-
- if (type.indexOf(".") >= 0) {
- // Namespaced trigger; create a regexp to match event type in handle()
- namespaces = type.split(".");
- type = namespaces.shift();
- namespaces.sort();
- }
- defaultGetter.type = type;
- defaultGetter.exclusive = exclusive;
-
- $event.handle.call(elem, defaultGetter, data);
- }
},
- checkAndRunDefaults : function(event, elem){
- //fire if there are default actions to run &&
- // we have not prevented default &&
- // propagation has been stopped or we are at the document element
- // we have reached the document
- if (!event.isDefaultPrevented() &&
- (!event.isPaused || !event.isPaused()) && // no paused function or it's not paused
- event._defaultActions &&
- ( ( event.isPropagationStopped() ) ||
- ( !elem.parentNode && !elem.ownerDocument ) )
-
- ) {
- var origNamespace = event.namespace,
- origType = event.type,
- origLiveFired = event.liveFired;
- // put event back
- event.namespace= event.type;
- event.type = "default";
- event.liveFired = null;
-
- // call each event handler
- for(var i = 0 ; i < event._defaultActions.length; i++){
- var a = event._defaultActions[i],
- oldHandle = event.handled;
- event.currentTarget = a.currentTarget;
- a.handler.call(a.element, event, a.data);
- event.handled = event.handled === null ? oldHandle : true;
- }
-
- event._defaultActions = null; //set to null so everyone else on this element ignores it
-
- if(event._success){
- event._success(event);
- }
-
- event.namespace= origNamespace;
- event.type = origType;
- event.liveFired = origLiveFired;
-
- }
- }
+ setup: function() {return true}
}
// overwrite trigger to allow default types
-var oldTrigger = $event.trigger,
- triggerDefault = $event.special['default'].triggerDefault,
- checkAndRunDefaults = $event.special['default'].checkAndRunDefaults,
- oldData = jQuery._data;
-
-$._data = function(elem, name, data){
- // always need to supply a function to call for handle
- if(!data && name === "handle"){
- var func = oldData.apply(this, arguments);
- return function(e){
- // Discard the second event of a jQuery.event.trigger() and
- // when an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
- jQuery.event.handle.apply( this, arguments ) :
- undefined;
- }
- }
- return oldData.apply(this, arguments)
-}
+var oldTrigger = $event.trigger;
$event.trigger = function defaultTriggerer( event, data, elem, onlyHandlers){
// Event object or event type
@@ -228,9 +128,26 @@ $event.trigger = function defaultTriggerer( event, data, elem, onlyHandlers){
// Just the event type (string)
new jQuery.Event( type );
- event._defaultActions = []; //set depth for possibly reused events
+ //event._defaultActions = []; //set depth for possibly reused events
+
+ var res = oldTrigger.call($.event, event, data, elem, onlyHandlers);
- oldTrigger.call($.event, event, data, elem, onlyHandlers);
+
+ if(!onlyHandlers && !event.isDefaultPrevented() && event.type.indexOf("default") !== 0){
+ oldTrigger("default."+event.type, data, elem)
+ if(event._success){
+ event._success(event)
+ }
+ }
+ // code for paused
+ if( event.isPaused && event.isPaused() ){
+ // set back original stuff
+ event.isDefaultPrevented =
+ event.pausedState.isDefaultPrevented;
+ event.isPropagationStopped =
+ event.pausedState.isPropagationStopped;
+ }
+ return res;
};
diff --git a/event/default/default_pause_test.js b/event/default/default_pause_test.js
index 6b49f415..4d9046c4 100644
--- a/event/default/default_pause_test.js
+++ b/event/default/default_pause_test.js
@@ -9,10 +9,12 @@ test("default and pause with delegate", function(){
$("#qunit-test-area").html("hello
This is a dummy page to show off your plugin
+ + + + \ No newline at end of file diff --git a/lang/observe/attributes/attributes.js b/lang/observe/attributes/attributes.js new file mode 100644 index 00000000..f03d0212 --- /dev/null +++ b/lang/observe/attributes/attributes.js @@ -0,0 +1,123 @@ +steal('jquery/lang/observe',function($){ + +var $Observe = $.Observe, + each = $.each, + extend = $.extend, + getObject = $.String.getObject; +// adds attributes, serialize, convert +extend($Observe,{ + attributes : {}, + convert: { + "date": function( str ) { + var type = typeof str; + if ( type === "string" ) { + return isNaN(Date.parse(str)) ? null : Date.parse(str) + } else if ( type === 'number' ) { + return new Date(str) + } else { + return str + } + }, + "number": function( val ) { + return parseFloat(val); + }, + "boolean": function( val ) { + return Boolean(val === "false" ? 0 : val); + }, + "default": function( val, error, type ) { + var construct = getObject(type), + context = window, + realType; + // if type has a . we need to look it up + if ( type.indexOf(".") >= 0 ) { + // get everything before the last . + realType = type.substring(0, type.lastIndexOf(".")); + // get the object before the last . + context = getObject(realType); + } + return typeof construct == "function" ? construct.call(context, val) : val; + } + }, + serialize: { + "default": function( val, type ) { + return isObject(val) && val.serialize ? val.serialize() : val; + }, + "date": function( val ) { + return val && val.getTime() + } + } +}); + +var proto = $Observe.prototype, + oldSet = proto.__set, + oldSetup = $Observe.setup; + + +proto.__set = function(prop, value, current, success, error){ + // check if there is a + + var Class = this.constructor, + // the value that we will set + val, + // the type of the attribute + type = Class.attributes[prop], + converter = Class.convert[type] || Class.convert['default']; + + oldSet.call(this, prop, + // if we get null or there is no type set + value === null || !type ? + // just use the value + value : + // otherwise, pass to the converter + converter.call(Class, value, function() {}, type), current, success, error ) +}; +proto.serialize = function(){ + var where = {}, + Class = this.constructor, + attrs = Class.attributes, + serialize = Class.serialize; + this.each(function( name, val ) { + var type = attrs[name], + converter= Class.serialize[type] + // if the value is an object, and has a attrs or serialize function + where[name] = val && typeof val.serialize == 'function' ? + // call attrs or serialize to get the original data back + val.serialize() : + // otherwise if we have a converter + converter ? + // use the converter + converter(val, type) : + // or return the val + val + }) + return where; +} +// overwrite setup to do this stuff +$Observe.setup = function(superClass, stat, proto){ + var self = this; + oldSetup.call(self, superClass, stat, proto); + + each(["attributes", "validations"], function( i, name ) { + if (!self[name] || superClass[name] === self[name] ) { + self[name] = {}; + } + }); + + each(["convert", "serialize"], function( i, name ) { + if ( superClass[name] != self[name] ) { + self[name] = extend({}, superClass[name], self[name]); + } + }); +}; + + +//add missing converters and serializes + + + + + + + + +}); \ No newline at end of file diff --git a/lang/observe/attributes/attributes_test.js b/lang/observe/attributes/attributes_test.js new file mode 100644 index 00000000..f563166c --- /dev/null +++ b/lang/observe/attributes/attributes_test.js @@ -0,0 +1,127 @@ +steal('funcunit/qunit','./attributes',function(){ + +module("attributes"); + +test("literal converters and serializes", function(){ + $.Observe("Task1",{ + attributes: { + createdAt: "date" + }, + convert: { + date: function(d){ + var months = ["jan", "feb", "mar"] + return months[d.getMonth()] + } + }, + serialize: { + date: function(d){ + var months = {"jan":0, "feb":1, "mar":2} + return months[d] + } + } + },{}); + $.Observe("Task2",{ + attributes: { + createdAt: "date" + }, + convert: { + date: function(d){ + var months = ["apr", "may", "jun"] + return months[d.getMonth()] + } + }, + serialize: { + date: function(d){ + var months = {"apr":0, "may":1, "jun":2} + return months[d] + } + } + },{}); + var d = new Date(); + d.setMonth(1) + var task1=new Task1({ + createdAt: d, + name:"Task1" + }); + d.setMonth(2) + var task2=new Task2({ + createdAt: d, + name:"Task2" + }); + equals(task1.createdAt, "feb", "Task1 model convert"); + equals(task2.createdAt, "jun", "Task2 model convert"); + equals(task1.serialize().createdAt, 1, "Task1 model serialize"); + equals(task2.serialize().createdAt, 2, "Task2 model serialize"); + equals(task1.serialize().name, "Task1", "Task1 model default serialized"); + equals(task2.serialize().name, "Task2", "Task2 model default serialized"); +}); + +var makeClasses= function(){ + $.Observe("AttrTest.Person", { + serialize: function() { + return "My name is " + this.name; + } + }); + $.Observe("AttrTest.Loan"); + $.Observe("AttrTest.Issue"); + + AttrTest.Person.model = function(data){ + return new this(data); + } + AttrTest.Loan.models = function(data){ + return $.map(data, function(l){ + return new AttrTest.Loan(l) + }); + } + AttrTest.Issue.models = function(data){ + return $.map(data, function(l){ + return new AttrTest.Issue(l) + }); + } + $.Observe("AttrTest.Customer", + { + attributes : { + person : "AttrTest.Person.model", + loans : "AttrTest.Loan.models", + issues : "AttrTest.Issue.models" + } + }, + {}); +} + +test("basic observe associations", function(){ + makeClasses(); + + var c = new AttrTest.Customer({ + person : { + id: 1, + name: "Justin" + }, + issues : [], + loans : [ + { + amount : 1000, + id: 2 + }, + { + amount : 19999, + id: 3 + } + ] + }); + + equals(c.person.name, "Justin", "association present"); + equals(c.person.Class, AttrTest.Person, "belongs to association typed"); + + equals(c.issues.length, 0); + + equals(c.loans.length, 2); + + equals(c.loans[0].Class, AttrTest.Loan); + + +}); + + + +}); \ No newline at end of file diff --git a/lang/observe/attributes/qunit.html b/lang/observe/attributes/qunit.html new file mode 100644 index 00000000..8581546d --- /dev/null +++ b/lang/observe/attributes/qunit.html @@ -0,0 +1,18 @@ + + + + +This is a dummy page to show off your plugin
+ + + + \ No newline at end of file diff --git a/lang/observe/setter/setter.js b/lang/observe/setter/setter.js new file mode 100644 index 00000000..8bb66376 --- /dev/null +++ b/lang/observe/setter/setter.js @@ -0,0 +1,31 @@ +steal('jquery/lang/observe',function($){ + + +var classize = $.String.classize, + proto = $.Observe.prototype, + old = proto.__set; + +proto.__set = function(prop, value, current, success, error){ + // check if there's a setter + var cap = classize(prop), + setName = "set" + cap, + errorCallback = function( errors ) { + var stub = error && error.call(self, errors); + $.event.trigger("error." + prop, errors, self, true); + }, + self = this; + // if we have a setter + if ( this[setName] && + // call the setter, if returned value is undefined, + // this means the setter is async so we + // do not call update property and return right away + ( value = this[setName](value, + function(){ old.call(self,prop, value, current, success, errorCallback) }, + errorCallback ) ) === undefined ) { + return; + } + old.call(self,prop, value, current, success, errorCallback); + return this; +}; + +}); \ No newline at end of file diff --git a/lang/observe/setter/setter_test.js b/lang/observe/setter/setter_test.js new file mode 100644 index 00000000..76da01ee --- /dev/null +++ b/lang/observe/setter/setter_test.js @@ -0,0 +1,24 @@ +steal('funcunit/qunit','./setter',function(){ + +module("Observe setter"); + +test("setter testing works", function(){ + + var Contact = $.Observe({ + setBirthday : function(raw){ + if(typeof raw == 'number'){ + return new Date( raw ) + }else if(raw instanceof Date){ + return raw; + } + } + }); + + var date = new Date(), + contact = new Contact({birthday: date.getTime()}); + + equals(contact.birthday.getTime(), date.getTime(), "set as birthday") +}); + + +}); \ No newline at end of file diff --git a/lang/observe/sort/sort.js b/lang/observe/sort/sort.js new file mode 100644 index 00000000..ac39aab9 --- /dev/null +++ b/lang/observe/sort/sort.js @@ -0,0 +1,56 @@ +/*, + sort: function(method, silent){ + var comparator = this.comparator, + args = comparator ? [function(a, b){ + a = a[comparator] + b = b[comparator] + return a === b ? 0 : (a < b ? -1 : 1); + }] : [], + res = [].sort.apply(this, args); + + !silent && trigger(this, "reset"); + + }*/ + + +if(this.comparator && /^\d+./.test(attr) ) { + + // get the index + var index = +/^\d+/.exec(attr)[0], + // and item + item = this[index], + // and the new item + newIndex = this.sortedIndex(item); + + if(newIndex !== index){ + // move ... + splice.call(this, index, 1); + splice.call(this, newIndex, 0, item); + + trigger(this, "move", [item, newIndex, index]); + ev.stopImmediatePropagation(); + trigger(this,"change", [ + attr.replace(/^\d+/,newIndex), + how, + newVal, + oldVal + ]); + return; + } + } + +sortedIndex : function(item){ + var itemCompare = item.attr(this.comparator), + equaled = 0, + i; + for(var i =0; i < this.length; i++){ + if(item === this[i]){ + equaled = -1; + continue; + } + if(itemCompare <= this[i].attr(this.comparator) ) { + return i+equaled; + } + } + return i+equaled; + }, \ No newline at end of file diff --git a/lang/observe/sort/sort_test.js b/lang/observe/sort/sort_test.js new file mode 100644 index 00000000..ee2be399 --- /dev/null +++ b/lang/observe/sort/sort_test.js @@ -0,0 +1,45 @@ +test("list events", function(){ + + var list = new $.Observe.List([ + {name: 'Justin'}, + {name: 'Brian'}, + {name: 'Austin'}, + {name: 'Mihael'}]) + list.comparator = 'name'; + list.sort(); + // events on a list + // - move - item from one position to another + // due to changes in elements that change the sort order + // - add (items added to a list) + // - remove (items removed from a list) + // - reset (all items removed from the list) + // - change something happened + + // a move directly on this list + list.bind('move', function(ev, item, newPos, oldPos){ + ok(true,"move called"); + equals(item.name, "Zed"); + equals(newPos, 3); + equals(oldPos, 0); + }); + + // a remove directly on this list + list.bind('remove', function(ev, items, oldPos){ + ok(true,"remove called"); + equals(items.length,1); + equals(items[0].name, 'Alexis'); + equals(oldPos, 0, "put in right spot") + }) + list.bind('add', function(ev, items, newPos){ + ok(true,"add called"); + equals(items.length,1); + equals(items[0].name, 'Alexis'); + equals(newPos, 0, "put in right spot") + }); + + list.push({name: 'Alexis'}); + + // now lets remove alexis ... + list.splice(0,1); + list[0].attr('name',"Zed") +}) \ No newline at end of file diff --git a/lang/string/string.js b/lang/string/string.js index e29fb807..049ab0c9 100644 --- a/lang/string/string.js +++ b/lang/string/string.js @@ -150,27 +150,17 @@ steal('jquery').then(function( $ ) { * @return {String} a string with the first character capitalized. */ capitalize: function( s, cache ) { + // used to make newId ... return s.charAt(0).toUpperCase() + s.substr(1); }, - /** - * Capitalizes a string from something undercored. Examples: - * @codestart - * jQuery.String.camelize("one_two") //-> "oneTwo" - * "three-four".camelize() //-> threeFour - * @codeend - * @param {String} s - * @return {String} a the camelized string - */ - camelize: function( s ) { - s = str.classize(s); - return s.charAt(0).toLowerCase() + s.substr(1); - }, /** * Like [jQuery.String.camelize|camelize], but the first part is also capitalized * @param {String} s * @return {String} the classized string */ classize: function( s , join) { + // this can be moved out .. + // used for getter setter var parts = s.split(regs.undHash), i = 0; for (; i < parts.length; i++ ) { @@ -179,18 +169,6 @@ steal('jquery').then(function( $ ) { return parts.join(join || ''); }, - /** - * Like [jQuery.String.classize|classize], but a space separates each 'word' - * @codestart - * jQuery.String.niceName("one_two") //-> "One Two" - * @codeend - * @param {String} s - * @return {String} the niceName - */ - niceName: function( s ) { - return str.classize(s,' '); - }, - /** * Underscores a string. * @codestart diff --git a/lang/string/string_test.js b/lang/string/string_test.js index 9d345984..106a2679 100644 --- a/lang/string/string_test.js +++ b/lang/string/string_test.js @@ -35,12 +35,12 @@ test("$.String.getObject", function(){ equals(obj,0, 'got 0 (falsey stuff)') }); - +/* test("$.String.niceName", function(){ var str = "some_underscored_string"; var niceStr = $.String.niceName(str); equals(niceStr, 'Some Underscored String', 'got correct niceName'); -}) +})*/ }).then('./deparam/deparam_test'); diff --git a/model/core_test.html b/model/core_test.html new file mode 100644 index 00000000..7fa32a2b --- /dev/null +++ b/model/core_test.html @@ -0,0 +1,16 @@ + + + + + + +This is a dummy page to show off your plugin
+ + + + \ No newline at end of file diff --git a/model/elements/elements.js b/model/elements/elements.js new file mode 100644 index 00000000..096aa7d6 --- /dev/null +++ b/model/elements/elements.js @@ -0,0 +1,145 @@ +steal('jquery',function($){ +var getId = function( inst ) { + return inst[inst.constructor.id] +}, +each = $.each, +// returns a collection of unique items +// this works on objects by adding a "__u Nique" property. +unique = function( items ) { + var collect = []; + // check unique property, if it isn't there, add to collect + each(items, function( i, item ) { + if (!item["__u Nique"] ) { + collect.push(item); + item["__u Nique"] = 1; + } + }); + // remove unique + return each(collect, function( i, item ) { + delete item["__u Nique"]; + }); +}; +$.extend($.Model.prototype, +{ + /** + * Returns a unique identifier for the model instance. For example: + * @codestart + * new Todo({id: 5}).identity() //-> 'todo_5' + * @codeend + * Typically this is used in an element's shortName property so you can find all elements + * for a model with [jQuery.Model.prototype.elements elements]. + * @return {String} + */ + identity: function() { + var id = getId(this), + constructor = this.constructor; + return (constructor._fullName + '_' + (constructor.escapeIdentity ? encodeURIComponent(id) : id)).replace(/ /g, '_'); + }, + /** + * Returns elements that represent this model instance. For this to work, your element's should + * us the [jQuery.Model.prototype.identity identity] function in their class name. Example: + * + *todos by
- * describing the services that can create, retrieve,
- * update, and delete todos.
- *
- * $.Model('Todo',{
- * findAll: 'GET /todos.json',
- * findOne: 'GET /todos/{id}.json',
- * create: 'POST /todos.json',
- * update: 'PUT /todos/{id}.json',
- * destroy: 'DELETE /todos/{id}.json'
- * },{});
- *
- * This lets you create, retrieve, update, and delete
- * todos programatically:
- *
- * __Create__
- *
- * Create a todo instance and
- * call [jQuery.Model.prototype.save save]( success, error )
- * to create the todo on the server.
- *
- * // create a todo instance
- * var todo = new Todo({name: "do the dishes"})
- *
- * // save it on the server
- * todo.save();
- *
- * __Retrieve__
- *
- * Retrieve a list of todos from the server with
- * findAll( params, callback( items ) ):
- *
- * Todo.findAll({}, function( todos ){
- *
- * // print out the todo names
- * $.each(todos, function(i, todo){
- * console.log( todo.name );
- * });
- * });
- *
- * Retrieve a single todo from the server with
- * findOne( params, callback( item ) ):
- *
- * Todo.findOne({id: 5}, function( todo ){
- *
- * // print out the todo name
- * console.log( todo.name );
- * });
- *
- * __Update__
- *
- * Once an item has been created on the server,
- * you can change its properties and call
- * save to update it on the server.
- *
- * // update the todos' name
- * todo.attr('name','Take out the trash')
- *
- * // update it on the server
- * todo.save()
- *
- *
- * __Destroy__
- *
- * Call [jQuery.Model.prototype.destroy destroy]( success, error )
- * to delete an item on the server.
- *
- * todo.destroy()
- *
- * ## Listen to changes in data
- *
- * Listening to changes in data is a critical part of
- * the [http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller Model-View-Controller]
- * architecture. $.Model lets you listen to when an item is created, updated, destroyed
- * and its properties are changed by creating [jquery.model.events events]
- * on the Model and on instances of the model.
- *
- * __Create__
- *
- * // listen for when any todo is created
- * Todo.bind('created', function( ev, todo ) {...})
- *
- * // listen for when a specific todo is created
- * var todo = new Todo({name: 'do dishes'})
- * todo.bind('created', function( ev ) {...})
- *
- * __Update__
- *
- * // listen for when any todo is updated
- * Todo.bind('updated', function( ev, todo ) {...})
- *
- * // listen for when a specific todo is created
- * Todo.findOne({id: 6}, function( todo ) {
- * todo.bind('updated', function( ev ) {...})
- * })
- *
- * __Destroy__
- *
- * // listen for when any todo is destroyed
- * Todo.bind('destroyed', function( ev, todo ) {...})
- *
- * // listen for when a specific todo is destroyed
- * todo.bind('destroyed', function( ev ) {...})
- *
- * __Property Changes__
- *
- * // listen for when the name property changes
- * todo.bind('name', function(ev){ })
- *
- * __Listening with Controller__
- *
- * You should be using controller to listen to model changes like:
- *
- * $.Controller('Todos',{
- * "{Todo} updated" : function(Todo, ev, todo) {...}
- * })
- *
- *
- * ## Setting and retrieving data on elements
- *
- * Almost always, we use HTMLElements to represent
- * data to the user. When that data changes, we update those
- * elements to reflect the new data.
- *
- * $.Model has helper methods that make this easy. They
- * let you "add" a model to an element and also find
- * all elements that have had a model "added" to them.
- *
- * Consider a todo list widget
- * that lists each todo in the page and when a todo is
- * deleted, removes it.
- *
- * [jQuery.fn.model $.fn.model]( item ) lets you set or read a model
- * instance from an element:
- *
- * Todo.findAll({}, function( todos ) {
- *
- * $.each(todos, function(todo) {
- * $('item.[jQuery.Model.prototype.elements elements]( context )
- * and remove it from the page.
- *
- * Todo.bind('destroyed', function( ev, todo ) {
- * todo.elements( $('#todos') ).remove()
- * })
- *
- * __Using EJS and $.Controller__
- *
- * [jQuery.View $.View] and [jQuery.EJS EJS] makes adding model data
- * to elements easy. We can implement the todos widget like the following:
- *
- * $.Controller('Todos',{
- * init: function(){
- * this.element.html('//todos/views/todos.ejs', Todo.findAll({}) );
- * },
- * "{Todo} destroyed": function(Todo, ev, todo) {
- * todo.elements( this.element ).remove()
- * }
- * })
- *
- * In todos.ejs
- *
- * @codestart html
- * <% for(var i =0; i < todos.length; i++){ %>
- * <li <%= todos[i] %>><%= todos[i].name %></li>
- * <% } %>
- * @codeend
- *
- * Notice how you can add a model to an element with <%= model %>
- *
- * ## Lists
- *
- * [jQuery.Model.List $.Model.List] lets you handle multiple model instances
- * with ease. A List acts just like an Array, but you can add special properties
- * to it and listen to events on it.
- *
- * $.Model.List has become its own plugin, read about it
- * [jQuery.Model.List here].
- *
- * ## Other Good Stuff
- *
- * Model can make a lot of other common tasks much easier.
- *
- * ### Type Conversion
- *
- * Data from the server often needs massaging to make it more useful
- * for JavaScript. A typical example is date data which is
- * commonly passed as
- * a number representing the Julian date like:
- *
- * { name: 'take out trash',
- * id: 1,
- * dueDate: 1303173531164 }
- *
- * But instead, you want a JavaScript date object:
- *
- * date.attr('dueDate') //-> new Date(1303173531164)
- *
- * By defining property-type pairs in [jQuery.Model.static.attributes attributes],
- * you can have model auto-convert values from the server into more useful types:
- *
- * $.Model('Todo',{
- * attributes : {
- * dueDate: 'date'
- * }
- * },{})
- *
- * ### Associations
- *
- * The [jQuery.Model.static.attributes attributes] property also
- * supports associations. For example, todo data might come back with
- * User data as an owner property like:
- *
- * { name: 'take out trash',
- * id: 1,
- * owner: { name: 'Justin', id: 3} }
- *
- * To convert owner into a User model, set the owner type as the User's
- * [jQuery.Model.static.model model]( data ) method:
- *
- * $.Model('Todo',{
- * attributes : {
- * owner: 'User.model'
- * }
- * },{})
- *
- * ### Helper Functions
- *
- * Often, you need to perform repeated calculations
- * with a model's data. You can create methods in the model's
- * prototype and they will be available on
- * all model instances.
- *
- * The following creates a timeRemaining method that
- * returns the number of seconds left to complete the todo:
- *
- * $.Model('Todo',{
- * },{
- * timeRemaining : function(){
- * return new Date() - new Date(this.dueDate)
- * }
- * })
- *
- * // create a todo
- * var todo = new Todo({dueDate: new Date()});
- *
- * // show off timeRemaining
- * todo.timeRemaining() //-> Number
- *
- * ### Deferreds
- *
- * Model methods that make requests to the server such as:
- * [jQuery.Model.static.findAll findAll], [jQuery.Model.static.findOne findOne],
- * [jQuery.Model.prototype.save save], and [jQuery.Model.prototype.destroy destroy] return a
- * [jquery.model.deferreds deferred] that resolves to the item(s)
- * being retrieved or modified.
- *
- * Deferreds can make a lot of asynchronous code much easier. For example, the following
- * waits for all users and tasks before continuing :
- *
- * $.when(Task.findAll(), User.findAll())
- * .then(function( tasksRes, usersRes ){ ... })
- *
- * ### Validations
- *
- * [jquery.model.validations Validate] your model's attributes.
- *
- * $.Model("Contact",{
- * init : function(){
- * this.validate("birthday",function(){
- * if(this.birthday > new Date){
- * return "your birthday needs to be in the past"
- * }
- * })
- * }
- * ,{});
- *
- *
- */
- // methods that we'll weave into model if provided
- ajaxMethods =
- /**
- * @Static
- */
- {
- create: function( str ) {
- /**
- * @function create
- * Create is used to create a model instance on the server. By implementing
- * create along with the rest of the [jquery.model.services service api], your models provide an abstract
- * API for services.
- *
- * Create is called by save to create a new instance. If you want to be able to call save on an instance
- * you have to implement create.
- *
- * The easiest way to implement create is to just give it the url to post data to:
- *
- * $.Model("Recipe",{
- * create: "/recipes"
- * },{})
- *
- * This lets you create a recipe like:
- *
- * new Recipe({name: "hot dog"}).save(function(){
- * this.name //this is the new recipe
- * }).save(callback)
- *
- * You can also implement create by yourself. You just need to call success back with
- * an object that contains the id of the new instance and any other properties that should be
- * set on the instance.
- *
- * For example, the following code makes a request
- * to '/recipes.json?name=hot+dog' and gets back
- * something that looks like:
- *
- * {
- * id: 5,
- * createdAt: 2234234329
- * }
- *
- * The code looks like:
- *
- * $.Model("Recipe", {
- * create : function(attrs, success, error){
- * $.post("/recipes.json",attrs, success,"json");
- * }
- * },{})
- *
- *
- * @param {Object} attrs Attributes on the model instance
- * @param {Function} success(attrs) the callback function, it must be called with an object
- * that has the id of the new instance and any other attributes the service needs to add.
- * @param {Function} error a function to callback if something goes wrong.
- */
- return function( attrs, success, error ) {
- return ajax(str || this._shortName, attrs, success, error, fixture(this, "Create", "-restCreate"))
- };
- },
- update: function( str ) {
- /**
- * @function update
- * Update is used to update a model instance on the server. By implementing
- * update along with the rest of the [jquery.model.services service api], your models provide an abstract
- * API for services.
- *
- * Update is called by [jQuery.Model.prototype.save] or [jQuery.Model.prototype.update]
- * on an existing model instance.
- *
- * The easist way to implement update is to just give it the url to put data to:
- *
- * $.Model("Recipe",{
- * update: "/recipes/{id}"
- * },{})
- *
- * This lets you update a recipe like:
- *
- * // PUT /recipes/5 {name: "Hot Dog"}
- * Recipe.update(5, {name: "Hot Dog"},
- * function(){
- * this.name //this is the updated recipe
- * })
- *
- * If your server doesn't use PUT, you can change it to post like:
- *
- * $.Model("Recipe",{
- * update: "POST /recipes/{id}"
- * },{})
- *
- * Your server should send back an object with any new attributes the model
- * should have. For example if your server udpates the "updatedAt" property, it
- * should send back something like:
- *
- * // PUT /recipes/4 {name: "Food"} ->
- * {
- * updatedAt : "10-20-2011"
- * }
- *
- * You can also implement create by yourself. You just need to call success back with
- * an object that contains any properties that should be
- * set on the instance.
- *
- * For example, the following code makes a request
- * to '/recipes/5.json?name=hot+dog' and gets back
- * something that looks like:
- *
- * {
- * updatedAt: "10-20-2011"
- * }
- *
- * The code looks like:
- *
- * $.Model("Recipe", {
- * update : function(id, attrs, success, error){
- * $.post("/recipes/"+id+".json",attrs, success,"json");
- * }
- * },{})
- *
- *
- * @param {String} id the id of the model instance
- * @param {Object} attrs Attributes on the model instance
- * @param {Function} success(attrs) the callback function. It optionally accepts
- * an object of attribute / value pairs of property changes the client doesn't already
- * know about. For example, when you update a name property, the server might
- * update other properties as well (such as updatedAt). The server should send
- * these properties as the response to updates. Passing them to success will
- * update the model instance with these properties.
- *
- * @param {Function} error a function to callback if something goes wrong.
- */
- return function( id, attrs, success, error ) {
- return ajax( str || this._shortName+"/{"+this.id+"}", addId(this, attrs, id), success, error, fixture(this, "Update", "-restUpdate"), "put")
- }
- },
- destroy: function( str ) {
- /**
- * @function destroy
- * Destroy is used to remove a model instance from the server. By implementing
- * destroy along with the rest of the [jquery.model.services service api], your models provide an abstract
- * service API.
- *
- * You can implement destroy with a string like:
- *
- * $.Model("Thing",{
- * destroy : "POST /thing/destroy/{id}"
- * })
- *
- * Or you can implement destroy manually like:
- *
- * $.Model("Thing",{
- * destroy : function(id, success, error){
- * $.post("/thing/destroy/"+id,{}, success);
- * }
- * })
- *
- * You just have to call success if the destroy was successful.
- *
- * @param {String|Number} id the id of the instance you want destroyed
- * @param {Function} success the callback function, it must be called with an object
- * that has the id of the new instance and any other attributes the service needs to add.
- * @param {Function} error a function to callback if something goes wrong.
- */
- return function( id, success, error ) {
- var attrs = {};
- attrs[this.id] = id;
- return ajax( str || this._shortName+"/{"+this.id+"}", attrs, success, error, fixture(this, "Destroy", "-restDestroy"), "delete")
- }
- },
-
- findAll: function( str ) {
- /**
- * @function findAll
- * FindAll is used to retrive a model instances from the server. By implementing
- * findAll along with the rest of the [jquery.model.services service api], your models provide an abstract
- * service API.
- * findAll returns a deferred ($.Deferred)
- *
- * You can implement findAll with a string:
- *
- * $.Model("Thing",{
- * findAll : "/things.json"
- * },{})
- *
- * Or you can implement it yourself. The 'dataType' attribute is used to convert a JSON array of attributes
- * to an array of instances. For example:
- *
- * $.Model("Thing",{
- * findAll : function(params, success, error){
- * return $.ajax({
- * url: '/things.json',
- * type: 'get',
- * dataType: 'json thing.models',
- * data: params,
- * success: success,
- * error: error})
- * }
- * },{})
- *
- *
- * @param {Object} params data to refine the results. An example might be passing {limit : 20} to
- * limit the number of items retrieved.
- * @param {Function} success(items) called with an array (or Model.List) of model instances.
- * @param {Function} error
- */
- return function( params, success, error ) {
- return ajax( str || this._shortName, params, success, error, fixture(this, "s"), "get", "json " + this._shortName + ".models");
- };
- },
- findOne: function( str ) {
- /**
- * @function findOne
- * FindOne is used to retrive a model instances from the server. By implementing
- * findOne along with the rest of the [jquery.model.services service api], your models provide an abstract
- * service API.
- *
- * You can implement findOne with a string:
- *
- * $.Model("Thing",{
- * findOne : "/things/{id}.json"
- * },{})
- *
- * Or you can implement it yourself.
- *
- * $.Model("Thing",{
- * findOne : function(params, success, error){
- * var self = this,
- * id = params.id;
- * delete params.id;
- * return $.get("/things/"+id+".json",
- * params,
- * success,
- * "json thing.model")
- * }
- * },{})
- *
- *
- * @param {Object} params data to refine the results. This is often something like {id: 5}.
- * @param {Function} success(item) called with a model instance
- * @param {Function} error
- */
- return function( params, success, error ) {
- return ajax(str || this._shortName+"/{"+this.id+"}", params, success, error, fixture(this), "get", "json " + this._shortName + ".model");
- };
- }
- };
-
-
-
-
-
- jQuery.Class("jQuery.Model", {
- setup: function( superClass, stat, proto ) {
-
- var self = this,
- fullName = this.fullName;
- //we do not inherit attributes (or validations)
- each(["attributes", "validations"], function( i, name ) {
- if (!self[name] || superClass[name] === self[name] ) {
- self[name] = {};
- }
- })
-
- //add missing converters and serializes
- each(["convert", "serialize"], function( i, name ) {
- if ( superClass[name] != self[name] ) {
- self[name] = extend({}, superClass[name], self[name]);
- }
- });
-
- this._fullName = underscore(fullName.replace(/\./g, "_"));
- this._shortName = underscore(this.shortName);
-
- if ( fullName.indexOf("jQuery") == 0 ) {
- return;
- }
-
- //add this to the collection of models
- //jQuery.Model.models[this._fullName] = this;
- if ( this.listType ) {
- this.list = new this.listType([]);
- }
- //@steal-remove-start
- if (!proto ) {
- steal.dev.warn("model.js " + fullName + " has no static properties. You probably need ,{} ")
- }
- //@steal-remove-end
- each(ajaxMethods, function(name, method){
- var prop = self[name];
- if ( typeof prop !== 'function' ) {
- self[name] = method(prop);
- }
- });
-
- //add ajax converters
- var converters = {},
- convertName = "* " + this._shortName + ".model";
-
- converters[convertName + "s"] = this.proxy('models');
- converters[convertName] = this.proxy('model');
-
- $.ajaxSetup({
- converters: converters
- });
- },
- /**
- * @attribute attributes
- * Attributes contains a map of attribute names/types.
- * You can use this in conjunction with
- * [jQuery.Model.static.convert] to provide automatic
- * [jquery.model.typeconversion type conversion] (including
- * associations).
- *
- * The following converts dueDates to JavaScript dates:
- *
- *
- * $.Model("Contact",{
- * attributes : {
- * birthday : 'date'
- * },
- * convert : {
- * date : function(raw){
- * if(typeof raw == 'string'){
- * var matches = raw.match(/(\d+)-(\d+)-(\d+)/)
- * return new Date( matches[1],
- * (+matches[2])-1,
- * matches[3] )
- * }else if(raw instanceof Date){
- * return raw;
- * }
- * }
- * }
- * },{})
- *
- * ## Associations
- *
- * Attribute type values can also represent the name of a
- * function. The most common case this is used is for
- * associated data.
- *
- * For example, a Deliverable might have many tasks and
- * an owner (which is a Person). The attributes property might
- * look like:
- *
- * attributes : {
- * tasks : "App.Models.Task.models"
- * owner: "App.Models.Person.model"
- * }
- *
- * This points tasks and owner properties to use
- * Task.models and Person.model
- * to convert the raw data into an array of Tasks and a Person.
- *
- * Note that the full names of the models themselves are App.Models.Task
- * and App.Models.Person. The _.model_ and _.models_ parts are appended
- * for the benefit of [jQuery.Model.static.convert convert] to identify the types as
- * models.
- *
- * @demo jquery/model/pages/associations.html
- *
- */
- attributes: {},
- /**
- * $.Model.model is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter]
- * to convert the response of a [jQuery.Model.static.findOne] request
- * into a model instance.
- *
- * You will never call this method directly. Instead, you tell $.ajax about it in findOne:
- *
- * $.Model('Recipe',{
- * findOne : function(params, success, error ){
- * return $.ajax({
- * url: '/services/recipes/'+params.id+'.json',
- * type: 'get',
- *
- * dataType : 'json recipe.model' //LOOK HERE!
- * });
- * }
- * },{})
- *
- * This makes the result of findOne a [http://api.jquery.com/category/deferred-object/ $.Deferred]
- * that resolves to a model instance:
- *
- * var deferredRecipe = Recipe.findOne({id: 6});
- *
- * deferredRecipe.then(function(recipe){
- * console.log('I am '+recipes.description+'.');
- * })
- *
- * ## Non-standard Services
- *
- * $.jQuery.model expects data to be name-value pairs like:
- *
- * {id: 1, name : "justin"}
- *
- * It can also take an object with attributes in a data, attributes, or
- * 'shortName' property. For a App.Models.Person model the following will all work:
- *
- * { data : {id: 1, name : "justin"} }
- *
- * { attributes : {id: 1, name : "justin"} }
- *
- * { person : {id: 1, name : "justin"} }
- *
- *
- * ### Overwriting Model
- *
- * If your service returns data like:
- *
- * {id : 1, name: "justin", data: {foo : "bar"} }
- *
- * This will confuse $.Model.model. You will want to overwrite it to create
- * an instance manually:
- *
- * $.Model('Person',{
- * model : function(data){
- * return new this(data);
- * }
- * },{})
- *
- *
- * @param {Object} attributes An object of name-value pairs or an object that has a
- * data, attributes, or 'shortName' property that maps to an object of name-value pairs.
- * @return {Model} an instance of the model
- */
- model: function( attributes ) {
- if (!attributes ) {
- return null;
- }
- if ( attributes instanceof this ) {
- attributes = attributes.serialize();
- }
- return new this(
- // checks for properties in an object (like rails 2.0 gives);
- isObject(attributes[this._shortName]) || isObject(attributes.data) || isObject(attributes.attributes) || attributes);
- },
- /**
- * $.Model.models is used as a [http://api.jquery.com/extending-ajax/#Converters Ajax converter]
- * to convert the response of a [jQuery.Model.static.findAll] request
- * into an array (or [jQuery.Model.List $.Model.List]) of model instances.
- *
- * You will never call this method directly. Instead, you tell $.ajax about it in findAll:
- *
- * $.Model('Recipe',{
- * findAll : function(params, success, error ){
- * return $.ajax({
- * url: '/services/recipes.json',
- * type: 'get',
- * data: params
- *
- * dataType : 'json recipe.models' //LOOK HERE!
- * });
- * }
- * },{})
- *
- * This makes the result of findAll a [http://api.jquery.com/category/deferred-object/ $.Deferred]
- * that resolves to a list of model instances:
- *
- * var deferredRecipes = Recipe.findAll({});
- *
- * deferredRecipes.then(function(recipes){
- * console.log('I have '+recipes.length+'recipes.');
- * })
- *
- * ## Non-standard Services
- *
- * $.jQuery.models expects data to be an array of name-value pairs like:
- *
- * [{id: 1, name : "justin"},{id:2, name: "brian"}, ...]
- *
- * It can also take an object with additional data about the array like:
- *
- * {
- * count: 15000 //how many total items there might be
- * data: [{id: 1, name : "justin"},{id:2, name: "brian"}, ...]
- * }
- *
- * In this case, models will return an array of instances found in
- * data, but with additional properties as expandos on the array:
- *
- * var people = Person.models({
- * count : 1500,
- * data : [{id: 1, name: 'justin'}, ...]
- * })
- * people[0].name // -> justin
- * people.count // -> 1500
- *
- * ### Overwriting Models
- *
- * If your service returns data like:
- *
- * {ballers: [{name: "justin", id: 5}]}
- *
- * You will want to overwrite models to pass the base models what it expects like:
- *
- * $.Model('Person',{
- * models : function(data){
- * return this._super(data.ballers);
- * }
- * },{})
- *
- * @param {Array} instancesRawData an array of raw name - value pairs.
- * @return {Array} a JavaScript array of instances or a [jQuery.Model.List list] of instances
- * if the model list plugin has been included.
- */
- models: function( instancesRawData ) {
- if (!instancesRawData ) {
- return null;
- }
- // get the list type
- var res = getList(this.List),
- // did we get an array
- arr = isArray(instancesRawData),
- // cache model list
- ML = $.Model.List,
- // did we get a model list?
- ml = (ML && instancesRawData instanceof ML),
- // get the raw array of objects
- raw = arr ?
- // if an array, return the array
- instancesRawData :
- // otherwise if a model list
- (ml ?
- // get the raw objects from the list
- instancesRawData.serialize() :
- // get the object's data
- instancesRawData.data),
- // the number of items
- length = raw.length,
- i = 0;
-
- //@steal-remove-start
- if (!length ) {
- steal.dev.warn("model.js models has no data. If you have one item, use model")
- }
- //@steal-remove-end
- for (; i < length; i++ ) {
- res.push(this.model(raw[i]));
- }
- if (!arr ) { //push other stuff onto array
- each(instancesRawData, function(prop, val){
- if ( prop !== 'data' ) {
- res[prop] = val;
- }
- })
- }
- return res;
- },
- /**
- * The name of the id field. Defaults to 'id'. Change this if it is something different.
- *
- * For example, it's common in .NET to use Id. Your model might look like:
- *
- * @codestart
- * $.Model("Friends",{
- * id: "Id"
- * },{});
- * @codeend
- */
- id: 'id',
- //if null, maybe treat as an array?
- /**
- * Adds an attribute to the list of attributes for this class.
- * @hide
- * @param {String} property
- * @param {String} type
- */
- addAttr: function( property, type ) {
- var stub, attrs = this.attributes;
-
- stub = attrs[property] || (attrs[property] = type);
- return type;
- },
- /**
- * @attribute convert
- * @type Object
- * An object of name-function pairs that are used to convert attributes.
- * Check out [jQuery.Model.static.attributes] or
- * [jquery.model.typeconversion type conversion]
- * for examples.
- *
- * Convert comes with the following types:
- *
- * - date - Converts to a JS date. Accepts integers or strings that work with Date.parse
- * - number - an integer or number that can be passed to parseFloat
- * - boolean - converts "false" to false, and puts everything else through Boolean()
- */
- convert: {
- "date": function( str ) {
- var type = typeof str;
- if ( type === "string" ) {
- return isNaN(Date.parse(str)) ? null : Date.parse(str)
- } else if ( type === 'number' ) {
- return new Date(str)
- } else {
- return str
- }
- },
- "number": function( val ) {
- return parseFloat(val);
- },
- "boolean": function( val ) {
- return Boolean(val === "false" ? 0 : val);
- },
- "default": function( val, error, type ) {
- var construct = getObject(type),
- context = window,
- realType;
- // if type has a . we need to look it up
- if ( type.indexOf(".") >= 0 ) {
- // get everything before the last .
- realType = type.substring(0, type.lastIndexOf("."));
- // get the object before the last .
- context = getObject(realType);
- }
- return typeof construct == "function" ? construct.call(context, val) : val;
- }
- },
- /**
- * @attribute serialize
- * @type Object
- * An object of name-function pairs that are used to serialize attributes.
- * Similar to [jQuery.Model.static.convert], in that the keys of this object
- * correspond to the types specified in [jQuery.Model.static.attributes].
- *
- * For example, to serialize all dates to ISO format:
- *
- *
- * $.Model("Contact",{
- * attributes : {
- * birthday : 'date'
- * },
- * serialize : {
- * date : function(val, type){
- * return new Date(val).toISOString();
- * }
- * }
- * },{})
- *
- * new Contact({ birthday: new Date("Oct 25, 1973") }).serialize()
- * // { "birthday" : "1973-10-25T05:00:00.000Z" }
- *
- */
- serialize: {
- "default": function( val, type ) {
- return isObject(val) && val.serialize ? val.serialize() : val;
- },
- "date": function( val ) {
- return val && val.getTime()
- }
- },
- bind: bind,
- unbind: unbind,
- _ajax: ajax
- },
- /**
- * @Prototype
- */
- {
- /**
- * Setup is called when a new model instance is created.
- * It adds default attributes, then whatever attributes
- * are passed to the class.
- * Setup should never be called directly.
- *
- * @codestart
- * $.Model("Recipe")
- * var recipe = new Recipe({foo: "bar"});
- * recipe.foo //-> "bar"
- * recipe.attr("foo") //-> "bar"
- * @codeend
- *
- * @param {Object} attributes a hash of attributes
- */
- setup: function( attributes ) {
- // so we know not to fire events
- this._init = true;
- this.attrs(extend({}, this.constructor.defaults, attributes));
- delete this._init;
- },
- /**
- * Sets the attributes on this instance and calls save.
- * The instance needs to have an id. It will use
- * the instance class's [jQuery.Model.static.update update]
- * method.
- *
- * @codestart
- * recipe.update({name: "chicken"}, success, error);
- * @codeend
- *
- * The model will also publish a _updated_ event with [jquery.model.events Model Events].
- *
- * @param {Object} attrs the model's attributes
- * @param {Function} success called if a successful update
- * @param {Function} error called if there's an error
- */
- update: function( attrs, success, error ) {
- this.attrs(attrs);
- return this.save(success, error); //on success, we should
- },
- /**
- * Runs the validations on this model. You can
- * also pass it an array of attributes to run only those attributes.
- * It returns nothing if there are no errors, or an object
- * of errors by attribute.
- *
- * To use validations, it's suggested you use the
- * model/validations plugin.
- *
- * $.Model("Task",{
- * init : function(){
- * this.validatePresenceOf("dueDate")
- * }
- * },{});
- *
- * var task = new Task(),
- * errors = task.errors()
- *
- * errors.dueDate[0] //-> "can't be empty"
- *
- * @param {Array} [attrs] an optional list of attributes to get errors for:
- *
- * task.errors(['dueDate']);
- *
- * @return {Object} an object of attributeName : [errors] like:
- *
- * task.errors() // -> {dueDate: ["cant' be empty"]}
- */
- errors: function( attrs ) {
- // convert attrs to an array
- if ( attrs ) {
- attrs = isArray(attrs) ? attrs : makeArray(arguments);
- }
- var errors = {},
- self = this,
- attr,
- // helper function that adds error messages to errors object
- // attr - the name of the attribute
- // funcs - the validation functions
- addErrors = function( attr, funcs ) {
- each(funcs, function( i, func ) {
- var res = func.call(self);
- if ( res ) {
- if (!errors[attr] ) {
- errors[attr] = [];
- }
- errors[attr].push(res);
- }
-
- });
- },
- validations = this.constructor.validations;
-
- // go through each attribute or validation and
- // add any errors
- each(attrs || validations || {}, function( attr, funcs ) {
- // if we are iterating through an array, use funcs
- // as the attr name
- if ( typeof attr == 'number' ) {
- attr = funcs;
- funcs = validations[attr];
- }
- // add errors to the
- addErrors(attr, funcs || []);
- });
- // return errors as long as we have one
- return $.isEmptyObject(errors) ? null : errors;
-
- },
- /**
- * Gets or sets an attribute on the model using setters and
- * getters if available.
- *
- * @codestart
- * $.Model("Recipe")
- * var recipe = new Recipe();
- * recipe.attr("foo","bar")
- * recipe.foo //-> "bar"
- * recipe.attr("foo") //-> "bar"
- * @codeend
- *
- * ## Setters
- *
- * If you add a setAttributeName method on your model,
- * it will be used to set the value. The set method is called
- * with the value and is expected to return the converted value.
- *
- * @codestart
- * $.Model("Recipe",{
- * setCreatedAt : function(raw){
- * return Date.parse(raw)
- * }
- * })
- * var recipe = new Recipe();
- * recipe.attr("createdAt","Dec 25, 1995")
- * recipe.createAt //-> Date
- * @codeend
- *
- * ## Asynchronous Setters
- *
- * Sometimes, you want to perform an ajax request when
- * you set a property. You can do this with setters too.
- *
- * To do this, your setter should return undefined and
- * call success with the converted value. For example:
- *
- * @codestart
- * $.Model("Recipe",{
- * setTitle : function(title, success, error){
- * $.post(
- * "recipe/update/"+this.id+"/title",
- * title,
- * function(){
- * success(title);
- * },
- * "json")
- * }
- * })
- *
- * recipe.attr("title","fish")
- * @codeend
- *
- * ## Events
- *
- * When you use attr, it can also trigger events. This is
- * covered in [jQuery.Model.prototype.bind].
- *
- * @param {String} attribute the attribute you want to set or get
- * @param {String|Number|Boolean} [value] value the value you want to set.
- * @param {Function} [success] an optional success callback.
- * This gets called if the attribute was successful.
- * @param {Function} [error] an optional success callback.
- * The error function is called with validation errors.
- */
- attr: function( attribute, value, success, error ) {
- // get the getter name getAttrName
- var cap = classize(attribute),
- get = "get" + cap;
-
- // if we are setting the property
- if ( value !== undefined ) {
- // the potential setter name
- var setName = "set" + cap,
- //the old value
- old = this[attribute],
- self = this,
- // if an error happens, this gets called
- // it calls back the error handler
- errorCallback = function( errors ) {
- var stub;
- stub = error && error.call(self, errors);
- trigger(self, "error." + attribute, errors);
- };
-
- // if we have a setter
- if ( this[setName] &&
- // call the setter, if returned value is undefined,
- // this means the setter is async so we
- // do not call update property and return right away
- (value = this[setName](value,
- // a success handler we pass to the setter, it needs to call
- // this if it returns undefined
- this.proxy('_updateProperty', attribute, value, old, success, errorCallback), errorCallback)) === undefined ) {
- return;
- }
- // call update property which will actually update the property
- this._updateProperty(attribute, value, old, success, errorCallback);
- return this;
- }
- // get the attribute, check if we have a getter, otherwise, just get the data
- return this[get] ? this[get]() : this[attribute];
- },
-
- /**
- * Binds to events on this model instance. Typically
- * you'll bind to an attribute name. Handler will be called
- * every time the attribute value changes. For example:
- *
- * @codestart
- * $.Model("School")
- * var school = new School();
- * school.bind("address", function(ev, address){
- * alert('address changed to '+address);
- * })
- * school.attr("address","1124 Park St");
- * @codeend
- *
- * You can also bind to attribute errors.
- *
- * @codestart
- * $.Model("School",{
- * setName : function(name, success, error){
- * if(!name){
- * error("no name");
- * }
- * return error;
- * }
- * })
- * var school = new School();
- * school.bind("error.name", function(ev, mess){
- * mess // -> "no name";
- * })
- * school.attr("name","");
- * @codeend
- *
- * You can also bind to created, updated, and destroyed events.
- *
- * @param {String} eventType the name of the event.
- * @param {Function} handler a function to call back when an event happens on this model.
- * @return {model} the model instance for chaining
- */
- bind: bind,
- /**
- * Unbinds an event handler from this instance.
- * Read [jQuery.Model.prototype.bind] for
- * more information.
- * @param {String} eventType
- * @param {Function} handler
- */
- unbind: unbind,
- // Actually updates a property on a model. This
- // - Triggers events when a property has been updated
- // - uses converters to change the data type
- // propety - the attribute name
- // value - the new value
- // old - the old value
- // success -
- _updateProperty: function( property, value, old, success, errorCallback ) {
- var Class = this.constructor,
- // the value that we will set
- val,
- // the type of the attribute
- type = Class.attributes[property] || Class.addAttr(property, "string"),
- //the converter
- converter = Class.convert[type] || Class.convert['default'],
- // errors for this property
- errors = null,
- // the event name prefix (might be error.)
- prefix = "",
- global = "updated.",
- args, globalArgs, callback = success,
- list = Class.list;
-
- // set the property value
- // notice that even if there's an error
- // property values get set
- val = this[property] =
- //if the value is null
- ( value === null ?
- // it should be null
- null :
- // otherwise, the converters to make it something useful
- converter.call(Class, value, function() {}, type) );
-
- //validate (only if not initializing, this is for performance)
- if (!this._init ) {
- errors = this.errors(property);
- }
- // args triggered on the property event name
- args = [val];
- // args triggered on the 'global' event (updated.attr)
- globalArgs = [property, val, old];
-
- // if there are errors, change props so we trigger error events
- if ( errors ) {
- prefix = global = "error.";
- callback = errorCallback;
- globalArgs.splice(1, 0, errors);
- args.unshift(errors)
- }
- // as long as we changed values, trigger events
- if ( old !== val && !this._init ) {
- !errors && trigger(this, prefix + property, args);
- trigger(this,global + "attr", globalArgs);
- }
- callback && callback.apply(this, args);
-
- //if this class has a global list, add / remove from the list.
- if ( property === Class.id && val !== null && list ) {
- // if we didn't have an old id, add ourselves
- if (!old ) {
- list.push(this);
- } else if ( old != val ) {
- // if our id has changed ... well this should be ok
- list.remove(old);
- list.push(this);
- }
- }
-
- },
-
- /**
- * Removes an attribute from the list existing of attributes.
- * Each attribute is set with [jQuery.Model.prototype.attr attr].
- *
- * @codestart
- * recipe.removeAttr('name')
- * @codeend
- *
- * @param {Object} [attribute] the attribute to remove
- */
- removeAttr: function( attr ) {
- var old = this[attr],
- deleted = false,
- attrs = this.constructor.attributes;
-
- //- pop it off the object
- if ( this[attr] ) {
- delete this[attr];
- }
-
- //- pop it off the Class attributes collection
- if ( attrs[attr] ) {
- delete attrs[attr];
- deleted = true;
- }
-
- //- trigger the update
- if (!this._init && deleted && old ) {
- trigger(this,"updated.attr", [attr, null, old]);
- }
- },
-
- /**
- * Gets or sets a list of attributes.
- * Each attribute is set with [jQuery.Model.prototype.attr attr].
- *
- * @codestart
- * recipe.attrs({
- * name: "ice water",
- * instructions : "put water in a glass"
- * })
- * @codeend
- *
- * This can be used nicely with [jquery.model.events].
- *
- * @param {Object} [attributes] if present, the list of attributes to send
- * @return {Object} the current attributes of the model
- */
- attrs: function( attributes ) {
- var key, constructor = this.constructor,
- attrs = constructor.attributes;
- if (!attributes ) {
- attributes = {};
- for ( key in attrs ) {
- if ( attrs.hasOwnProperty(key) ) {
- attributes[key] = this.attr(key);
- }
- }
- } else {
- var idName = constructor.id;
- //always set the id last
- for ( key in attributes ) {
- if ( key != idName ) {
- this.attr(key, attributes[key]);
- }
- }
- if ( idName in attributes ) {
- this.attr(idName, attributes[idName]);
- }
-
- }
- return attributes;
- },
- /**
- * Get a serialized object for the model. Serialized data is typically
- * used to send back to a server. See [jQuery.Model.static.serialize].
- *
- * model.serialize() // -> { name: 'Fred' }
- *
- * @return {Object} a JavaScript object that can be serialized with
- * `JSON.stringify` or other methods.
- */
- serialize: function() {
- var Class = this.constructor,
- attrs = Class.attributes,
- type, converter, data = {},
- attr;
-
- attributes = {};
-
- for ( attr in attrs ) {
- if ( attrs.hasOwnProperty(attr) ) {
- type = attrs[attr];
- // the attribute's converter or the default converter for the class
- converter = Class.serialize[type] || Class.serialize['default'];
- data[attr] = converter(this[attr], type);
- }
- }
- return data;
- },
- /**
- * Returns if the instance is a new object. This is essentially if the
- * id is null or undefined.
- *
- * new Recipe({id: 1}).isNew() //-> false
- * @return {Boolean} false if an id is set, true if otherwise.
- */
- isNew: function() {
- var id = getId(this);
- return (id === undefined || id === null || id === ''); //if null or undefined
- },
- /**
- * Creates or updates the instance using [jQuery.Model.static.create] or
- * [jQuery.Model.static.update] depending if the instance
- * [jQuery.Model.prototype.isNew has an id or not].
- *
- * When a save is successful, `success` is called and depending if the
- * instance was created or updated, a created or updated event is fired.
- *
- * ### Example
- *
- * $.Model('Recipe',{
- * created : "/recipes",
- * updated : "/recipes/{id}.json"
- * },{})
- *
- * // create a new instance
- * var recipe = new Recipe({name: "ice water"});
- *
- * // listen for when it is created or updated
- * recipe.bind('created', function(ev, recipe){
- * console.log('created', recipe.id)
- * }).bind('updated', function(ev, recipe){
- * console.log('updated', recipe.id );
- * })
- *
- * // create the recipe on the server
- * recipe.save(function(){
- * // update the recipe's name
- * recipe.attr('name','Ice Water');
- *
- * // update the recipe on the server
- * recipe.save();
- * }, error);
- *
- *
- * @param {Function} [success(instance,data)] called if a successful save.
- * @param {Function} [error(jqXHR)] error handler function called if the
- * save was not successful. It is passed the ajax request's jQXHR object.
- * @return {$.Deferred} a jQuery deferred that resolves to the instance, but
- * after it has been created or updated.
- */
- save: function( success, error ) {
- return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
- },
-
- /**
- * Destroys the instance by calling
- * [jQuery.Model.static.destroy] with the id of the instance.
- *
- * @codestart
- * recipe.destroy(success, error);
- * @codeend
- *
- * If OpenAjax.hub is available, after a successful
- * destroy "modelName.destroyed" is published
- * with the model instance.
- *
- * @param {Function} [success] called if a successful destroy
- * @param {Function} [error] called if an unsuccessful destroy
- */
- destroy: function( success, error ) {
- return makeRequest(this, 'destroy', success, error, 'destroyed');
- },
-
-
- /**
- * Returns a unique identifier for the model instance. For example:
- * @codestart
- * new Todo({id: 5}).identity() //-> 'todo_5'
- * @codeend
- * Typically this is used in an element's shortName property so you can find all elements
- * for a model with [jQuery.Model.prototype.elements elements].
- * @return {String}
- */
- identity: function() {
- var id = getId(this),
- constructor = this.constructor;
- return (constructor._fullName + '_' + (constructor.escapeIdentity ? encodeURIComponent(id) : id)).replace(/ /g, '_');
- },
- /**
- * Returns elements that represent this model instance. For this to work, your element's should
- * us the [jQuery.Model.prototype.identity identity] function in their class name. Example:
- *
- * attrs - an Object of attribute / value pairs
- * - success([attrs]) - Create calls success when the request has completed
- * successfully. Success can be called back with an object that represents
- * additional properties that will be set on the instance. For example, the server might
- * send back an updatedAt date.
- * - error - Create should callback error if an error happens during the request
- * - deferred - A deferred that gets resolved to any additional attrs
- * that might need to be set on the model instance.
- *
- *
- * ### findAll( params, success(items), error) -> deferred
- *
- *
- * - params - an Object that filters the items returned
- * - success(items) - success should be called with an Array of Model instances.
- * - error - called if an error happens during the request
- * - deferred - A deferred that gets resolved to the list of items
- *
- * ### findOne(params, success(items), error) -> deferred
- *
- * - params - an Object that filters the item returned
- * - success(item) - success should be called with a model instance.
- * - error - called if an error happens during the request
- * - deferred - A deferred that gets resolved to a model instance
- *
- * ### update(id, attrs, success([attrs]), error()) -> deferred
- *
- * - id - the id of the instance you are updating
- * - attrs - an Object of attribute / value pairs
- * - success([attrs]) - Call success when the request has completed
- * successfully. Success can be called back with an object that represents
- * additional properties that will be set on the instance. For example, the server might
- * send back an updatedAt date.
- * - error - Callback error if an error happens during the request
- * - deferred - A deferred that gets resolved to any additional attrs
- * that might need to be set on the model instance.
- *
- * ### destroy(id, success([attrs]), error()) -> deferred
- *
- * - id - the id of the instance you are destroying
- * - success([attrs]) - Calls success when the request has completed
- * successfully. Success can be called back with an object that represents
- * additional properties that will be set on the instance.
- * - error - Create should callback error if an error happens during the request
- * - deferred - A deferred that gets resolved to any additional attrs
- * that might need to be set on the model instance.
- */
-});
\ No newline at end of file
+// get observe and all of it's plugins
+steal('jquery/lang/observe/setter',
+ 'jquery/lang/observe/attributes')
+.then('./model_core')
+.then('./elements/elements.js')
diff --git a/model/model_core.js b/model/model_core.js
new file mode 100644
index 00000000..5d603b3b
--- /dev/null
+++ b/model/model_core.js
@@ -0,0 +1,260 @@
+// this file should not be stolen directly
+steal('jquery/lang/observe',function(){
+
+ var getId = function( inst ) {
+ return inst[inst.constructor.id]
+ },
+ trigger = function(obj, event, args){
+ $.event.trigger(event, args, obj, true)
+ },
+ ajax = function(ajaxOb, data, type, dataType, success, error ) {
+
+
+ // if we get a string, handle it
+ if ( typeof ajaxOb == "string" ) {
+ // if there's a space, it's probably the type
+ var parts = ajaxOb.split(" ")
+ ajaxOb = {
+ url : parts.pop(),
+ type : parts.pop()
+ };
+ }
+
+ // if we are a non-array object, copy to a new attrs
+ ajaxOb.data = typeof data == "object" && !$.isArray(data) ?
+ $.extend(ajaxOb.data || {}, data) : data;
+
+
+ // get the url with any templated values filled out
+ ajaxOb.url = $.String.sub(ajaxOb.url, ajaxOb.data, true);
+
+ return $.ajax($.extend({
+ type: type || "post",
+ dataType: dataType ||"json",
+ success : success,
+ error: error
+ },ajaxOb));
+ },
+ makeRequest = function( self, type, success, error, method ) {
+ var deferred ,
+ args = [self.serialize()],
+ // the Model
+ model = self.constructor,
+ jqXHR;
+
+ // destroy does not need data
+ if ( type == 'destroy' ) {
+ args.shift();
+ }
+ // update and destroy need the id
+ if ( type !== 'create' ) {
+ args.unshift(getId(self))
+ }
+
+ jqXHR = model[type].apply(model, args);
+
+ deferred = jqXHR.pipe(function(data){
+ self[method || type + "d"](data, jqXHR);
+ return self
+ })
+ promise = deferred.promise();
+ // hook up abort
+ if(jqXHR.abort){
+ promise.abort = function(){
+ jqXHR.abort();
+ }
+ }
+
+ return promise.then(success)
+ .fail(error);
+ }
+
+ // 338
+ ajaxMethods =
+ /**
+ * @Static
+ */
+ {
+ create: function( str , method) {
+ return function( attrs ) {
+ return ajax(str || this._shortName, attrs)
+ };
+ },
+ update: function( str ) {
+ return function( id, attrs ) {
+
+ // move id to newId if changing id
+ attrs = attrs || {};
+ var identity = this.id;
+ if ( attrs[identity] && attrs[identity] !== id ) {
+ attrs["new" + $.String.capitalize(id)] = attrs[identity];
+ delete attrs[identity];
+ }
+ attrs[identity] = id;
+
+ return ajax( str || this._url, attrs, "put")
+ }
+ },
+ destroy: function( str ) {
+ return function( id ) {
+ var attrs = {};
+ attrs[this.id] = id;
+ return ajax( str || this._url, attrs, "delete")
+ }
+ },
+
+ findAll: function( str ) {
+ return function( params, success, error ) {
+ return ajax( str || this._shortName, params, "get", "json " + this.fullName + ".models", success, error);
+ };
+ },
+ findOne: function( str ) {
+ return function( params, success, error ) {
+ return ajax(str || this._url, params, "get", "json " + this.fullName + ".model", success, error);
+ };
+ }
+ };
+ var modelNum = 0;
+ $.Observe("jQuery.Model",{
+ setup : function(){
+ $.Observe.apply(this, arguments);
+ if(this === jQuery.Model){
+ return;
+ }
+ var self = this;
+
+ $.each(ajaxMethods, function(name, method){
+ var prop = self[name];
+ if ( typeof prop !== 'function' ) {
+ self[name] = method(prop);
+ }
+ });
+ if(self.fullName == "jQuery.Model"){
+ self.fullName = "Model"+(++modelNum);
+ }
+ //add ajax converters
+ var converters = {},
+ convertName = "* " + self.fullName + ".model";
+
+ converters[convertName + "s"] = $.proxy(self.models,self);
+ converters[convertName] = $.proxy(self.model,self);
+
+ $.ajaxSetup({
+ converters: converters
+ });
+ this._url = this._shortName+"/{"+this.id+"}"
+ },
+ id: "id",
+ models: function( instancesRawData ) {
+ if (!instancesRawData ) {
+ return null;
+ }
+ // get the list type
+ var res = new( this.List || ML),
+ // did we get an array
+ arr = $.isArray(instancesRawData),
+
+ // did we get a model list?
+ ml = (instancesRawData instanceof ML),
+ // get the raw array of objects
+ raw = arr ?
+ // if an array, return the array
+ instancesRawData :
+ // otherwise if a model list
+ (ml ?
+ // get the raw objects from the list
+ instancesRawData.serialize() :
+ // get the object's data
+ instancesRawData.data),
+ // the number of items
+ length = raw.length,
+ i = 0;
+
+ //@steal-remove-start
+ if (!length ) {
+ steal.dev.warn("model.js models has no data. If you have one item, use model")
+ }
+ //@steal-remove-end
+ for (; i < length; i++ ) {
+ res.push(this.model(raw[i]));
+ }
+ if (!arr ) { //push other stuff onto array
+ $.each(instancesRawData, function(prop, val){
+ if ( prop !== 'data' ) {
+ res[prop] = val;
+ }
+ })
+ }
+ return res;
+ },
+ model: function( attributes ) {
+ if (!attributes ) {
+ return null;
+ }
+ if ( attributes instanceof this ) {
+ attributes = attributes.serialize();
+ }
+ return new this( attributes );
+ }
+ },{
+ isNew: function() {
+ var id = getId(this);
+ // id || id === 0?
+ return (id === undefined || id === null || id === ''); //if null or undefined
+ },
+ save: function( success, error ) {
+ return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
+ },
+ destroy: function( success, error ) {
+ return makeRequest(this, 'destroy', success, error, 'destroyed');
+ }
+ });
+
+ $.each([
+ /**
+ * @function created
+ * @hide
+ * Called by save after a new instance is created. Publishes 'created'.
+ * @param {Object} attrs
+ */
+ "created",
+ /**
+ * @function updated
+ * @hide
+ * Called by save after an instance is updated. Publishes 'updated'.
+ * @param {Object} attrs
+ */
+ "updated",
+ /**
+ * @function destroyed
+ * @hide
+ * Called after an instance is destroyed.
+ * - Publishes "shortName.destroyed".
+ * - Triggers a "destroyed" event on this model.
+ * - Removes the model from the global list if its used.
+ *
+ */
+ "destroyed"], function( i, funcName ) {
+ $.Model.prototype[funcName] = function( attrs ) {
+ var stub,
+ constructor = this.constructor;
+
+ // update attributes if attributes have been passed
+ stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs);
+
+ // call event on the instance
+ trigger(this,funcName);
+
+ //@steal-remove-start
+ steal.dev.log("Model.js - "+ constructor.shortName+" "+ funcName);
+ //@steal-remove-end
+
+ // call event on the instance's Class
+ trigger(constructor,funcName, this);
+ };
+ });
+
+
+ var ML = $.Observe.List('jQuery.Model.List')
+
+})
diff --git a/model/test/qunit/associations_test.js b/model/test/qunit/associations_test.js
index 7e8a8559..e82923e0 100644
--- a/model/test/qunit/associations_test.js
+++ b/model/test/qunit/associations_test.js
@@ -17,7 +17,7 @@ module("jquery/model/associations",{
issues : "MyTest.Issue.models"
},
- update : function(id, attrs, success, error){
+ update : function(id, attrs){
return $.ajax({
url : "/people/"+id,
data : attrs,
@@ -25,11 +25,11 @@ module("jquery/model/associations",{
dataType : "json",
fixture: function(){
return [{
+ // moving despite saving?
loansAttr: attrs.loans,
personAttr: attrs.person
}]
- },
- success : success
+ }
})
}
},
@@ -111,10 +111,12 @@ test("Model.List association serialize on save", function(){
stop();
cSave.then(function(customer){
start()
- ok(customer.loansAttr._namespace === undefined, "_namespace does not exist");
+ ok(true, "called back")
+ equals(customer.loansAttr.constructor, $.Observe.List, "we get an observe list back")
+ /*ok(customer.loansAttr._namespace === undefined, "_namespace does not exist");
ok(customer.loansAttr._data === undefined, "_data does not exist");
ok(customer.loansAttr._use_call === undefined, "_use_call does not exist");
- ok(customer.loansAttr._changed === undefined, "_changed does not exist");
+ ok(customer.loansAttr._changed === undefined, "_changed does not exist");*/
});
diff --git a/model/test/qunit/model_test.js b/model/test/qunit/model_test.js
index b9ffac43..22a0500f 100644
--- a/model/test/qunit/model_test.js
+++ b/model/test/qunit/model_test.js
@@ -1,40 +1,14 @@
module("jquery/model", {
setup: function() {
- var ids = 0;
- $.Model("Person",{
- findAll: function( params, success, error ) {
- success("findAll");
- },
- findOne: function( params, success, error ) {
- success("findOne");
- },
- create: function( params, success, error ) {
- success({zoo: "zed", id: (++ids)},"create");
- },
- destroy: function( id, success, error ) {
- success("destroy");
- },
- update: function( id, attrs, success, error ) {
- success({zoo: "monkeys"},"update");
- }
- },{
- prettyName: function() {
- return "Mr. "+this.name;
- }
- })
+
}
})
test("CRUD", function(){
-
- Person.findAll({}, function(response){
- equals("findAll", response)
- })
- Person.findOne({}, function(response){
- equals("findOne", response)
- })
- var person;
+
+
+ return;
new Person({foo: "bar"}).save(function(inst, attrs, create){
equals(create, "create")
equals("bar", inst.foo)
@@ -54,7 +28,7 @@ test("findAll deferred", function(){
return $.ajax({
url : "/people",
data : params,
- dataType : "json person.models",
+ dataType : "json Person.models",
fixture: "//jquery/model/test/people.json"
})
}
@@ -70,12 +44,12 @@ test("findAll deferred", function(){
});
test("findOne deferred", function(){
- $.Model.extend("Person",{
+ $.Model("Person",{
findOne : function(params, success, error){
return $.ajax({
url : "/people/5",
data : params,
- dataType : "json person.model",
+ dataType : "json Person.model",
fixture: "//jquery/model/test/person.json"
})
}
@@ -176,6 +150,7 @@ test("destroy deferred", function(){
test("hookup and model", function(){
+ $.Model('Person')
var div = $("")
var p = new Person({foo: "bar2", id: 5});
p.hookup( div[0] );
@@ -201,6 +176,11 @@ test("unique models", function(){
test("models", function(){
+ $.Model("Person",{
+ prettyName : function(){
+ return "Mr. "+this.name;
+ }
+ })
var people = Person.models([
{id: 1, name: "Justin"}
])
@@ -244,6 +224,7 @@ test("async setters", function(){
})
test("binding", 2,function(){
+ $.Model('Person')
var inst = new Person({foo: "bar"});
inst.bind("foo", function(ev, val){
@@ -256,7 +237,8 @@ test("binding", 2,function(){
});
test("error binding", 1, function(){
- $.Model.extend("School",{
+
+ $.Model("School",{
setName : function(name, success, error){
if(!name){
error("no name");
@@ -292,11 +274,13 @@ test("auto methods",function(){
equals(school.constructor.shortName,"School","a single school");
- new School({name: "Highland"}).save(function(){
- equals(this.name,"Highland","create gets the right name")
- this.update({name: "LHS"}, function(){
+ new School({name: "Highland"}).save(function(school){
+
+ equals(school.name,"Highland","create gets the right name")
+
+ school.attr({name: "LHS"}).save( function(){
start();
- equals(this.name,"LHS","create gets the right name")
+ equals(school.name,"LHS","create gets the right name")
$.fixture.on = true;
})
@@ -329,6 +313,8 @@ test("findAll string", function(){
})
})
test("Empty uses fixtures", function(){
+ ok(false, "Figure out")
+ return;
$.Model("Test.Things");
$.fixture.make("thing", 10, function(i){
return {
@@ -343,26 +329,27 @@ test("Empty uses fixtures", function(){
});
test("Model events" , function(){
+
var order = 0;
$.Model("Test.Event",{
- create : function(attrs, success){
- success({id: 1})
+ create : function(attrs){
+ return $.Deferred().resolve({id: 1})
},
update : function(id, attrs, success){
- success(attrs)
+ return $.Deferred().resolve(attrs)
},
destroy : function(id, success){
- success()
+ return $.Deferred().resolve({})
}
},{});
stop();
- $([Test.Event]).bind('created',function(ev, passedItem){
+ Test.Event.bind('created',function(ev, passedItem){
ok(this === Test.Event, "got model")
ok(passedItem === item, "got instance")
equals(++order, 1, "order");
- passedItem.update({});
+ passedItem.save();
}).bind('updated', function(ev, passedItem){
equals(++order, 2, "order");
@@ -446,12 +433,13 @@ test("default converters", function(){
})
test("removeAttr test", function(){
+ $.Model("Person");
var person = new Person({foo: "bar"})
equals(person.foo, "bar", "property set");
person.removeAttr('foo')
equals(person.foo, undefined, "property removed");
- var attrs = person.attrs()
+ var attrs = person.attr()
equals(attrs.foo, undefined, "attrs removed");
});
diff --git a/model/test/qunit/qunit.js b/model/test/qunit/qunit.js
index bf3583db..c1df2e98 100644
--- a/model/test/qunit/qunit.js
+++ b/model/test/qunit/qunit.js
@@ -2,8 +2,4 @@
steal("jquery/model","jquery/dom/fixture") //load your app
.then('funcunit/qunit') //load qunit
.then("./model_test.js","./associations_test.js")
- .then(
- "jquery/model/backup/qunit",
- "jquery/model/list/list_test.js"
- )
- .then("jquery/model/validations/qunit/validations_test.js")
+
\ No newline at end of file
diff --git a/mvc/jquery.mvc.js b/mvc/jquery.mvc.js
new file mode 100644
index 00000000..d5cbf67d
--- /dev/null
+++ b/mvc/jquery.mvc.js
@@ -0,0 +1,4648 @@
+(function( $ ) {
+ // Several of the methods in this plugin use code adapated from Prototype
+ // Prototype JavaScript framework, version 1.6.0.1
+ // (c) 2005-2007 Sam Stephenson
+ var regs = {
+ undHash: /_|-/,
+ colons: /::/,
+ words: /([A-Z]+)([A-Z][a-z])/g,
+ lowUp: /([a-z\d])([A-Z])/g,
+ dash: /([a-z\d])([A-Z])/g,
+ replacer: /\{([^\}]+)\}/g,
+ dot: /\./
+ },
+ // gets the nextPart property from current
+ // add - if true and nextPart doesnt exist, create it as an empty object
+ getNext = function(current, nextPart, add){
+ return current[nextPart] !== undefined ? current[nextPart] : ( add && (current[nextPart] = {}) );
+ },
+ // returns true if the object can have properties (no nulls)
+ isContainer = function(current){
+ var type = typeof current;
+ return current && ( type == 'function' || type == 'object' );
+ },
+ // a reference
+ getObject,
+ /**
+ * @class jQuery.String
+ * @parent jquerymx.lang
+ *
+ * A collection of useful string helpers. Available helpers are:
+ *
+ * $.String.sub("foo {bar}",{bar: "far"})
+ * //-> "foo far"
+ * this._super. Lets create a SeaMonster class. SeaMonsters are less
+ * efficient at eating small children, but more powerful fighters.
+ *
+ *
+ * Monster("SeaMonster",{
+ * eat: function( smallChildren ) {
+ * this._super(smallChildren / 2);
+ * },
+ * fight: function() {
+ * this.health -= 1;
+ * }
+ * });
+ *
+ * lockNess = new SeaMonster('Lock Ness');
+ * lockNess.eat(4); //health = 12
+ * lockNess.fight(); //health = 11
+ *
+ * ### Static property inheritance
+ *
+ * You can also inherit static properties in the same way:
+ *
+ * $.Class("First",
+ * {
+ * staticMethod: function() { return 1;}
+ * },{})
+ *
+ * First("Second",{
+ * staticMethod: function() { return this._super()+1;}
+ * },{})
+ *
+ * Second.staticMethod() // -> 2
+ *
+ * ## Namespaces
+ *
+ * Namespaces are a good idea! We encourage you to namespace all of your code.
+ * It makes it possible to drop your code into another app without problems.
+ * Making a namespaced class is easy:
+ *
+ *
+ * $.Class("MyNamespace.MyClass",{},{});
+ *
+ * new MyNamespace.MyClass()
+ *
+ *
+ * + * Class provides static and prototype initialization functions. + * These come in two flavors - setup and init. + * Setup is called before init and + * can be used to 'normalize' init's arguments. + *
+ *init method
+ * gets called in the following example:
+ *
+ * $.Class("Foo", {
+ * init: function( arg1, arg2, arg3 ) {
+ * this.sum = arg1+arg2+arg3;
+ * }
+ * })
+ * var foo = new Foo(1,2,3);
+ * foo.sum //-> 6
+ *
+ * ## Proxies
+ *
+ * Similar to jQuery's proxy method, Class provides a
+ * [jQuery.Class.static.proxy proxy]
+ * function that returns a callback to a method that will always
+ * have
+ * this set to the class or instance of the class.
+ *
+ *
+ * The following example uses this.proxy to make sure
+ * this.name is available in show.
+ *
+ * $.Class("Todo",{
+ * init: function( name ) {
+ * this.name = name
+ * },
+ * get: function() {
+ * $.get("/stuff",this.proxy('show'))
+ * },
+ * show: function( txt ) {
+ * alert(this.name+txt)
+ * }
+ * })
+ * new Todo("Trash").get()
+ *
+ * Callback is available as a static and prototype method.
+ *
+ * ## Demo
+ *
+ * @demo jquery/class/class.html
+ *
+ *
+ * @constructor
+ *
+ * To create a Class call:
+ *
+ * $.Class( [NAME , STATIC,] PROTOTYPE ) -> Class
+ *
+ * {optional:String}
+ * If provided, this sets the shortName and fullName of the + * class and adds it and any necessary namespaces to the + * window object.
+ *{optional:Object}
+ * If provided, this creates static properties and methods + * on the class.
+ *{Object}
+ * Creates prototype methods on the class.
+ *.extend. Instead, you can pass those options directly to
+ * $.Class (and any inheriting classes):
+ *
+ * // with className, static and prototype functions
+ * $.Class('Task',{ STATIC },{ PROTOTYPE })
+ * // with just classname and prototype functions
+ * $.Class('Task',{ PROTOTYPE })
+ * // with just a className
+ * $.Class('Task')
+ *
+ * @param {String} [fullName] the classes name (used for classes w/ introspection)
+ * @param {Object} [klass] the new classes static/class functions
+ * @param {Object} [proto] the new classes prototype functions
+ *
+ * @return {jQuery.Class} returns the new class
+ */
+ extend: function( fullName, klass, proto ) {
+ // 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,
+ name, 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
+ this._inherit(proto, _super, prototype);
+
+ // The dummy class constructor
+ function Class() {
+ // All construction is actually done in the init method
+ if ( initializing ) return;
+
+ // we are being called w/o new, we are extending
+ if ( this.constructor !== Class && arguments.length ) {
+ return arguments.callee.extend.apply(arguments.callee, arguments)
+ } else { //we are being called w/ new
+ return this.Class.newInstance.apply(this.Class, arguments)
+ }
+ }
+ // Copy old stuff onto class (can probably be merged w/ inherit)
+ for ( name in this ) {
+ if ( this.hasOwnProperty(name) ) {
+ Class[name] = this[name];
+ }
+ }
+ // copy new static props on class
+ this._inherit(klass, this, Class);
+
+ // do namespace stuff
+ if ( fullName ) {
+
+ var parts = fullName.split(/\./),
+ shortName = parts.pop(),
+ current = $.String.getObject(parts.join('.'), window, true),
+ namespace = current,
+ _fullName = underscore(fullName.replace(/\./g, "_")),
+ _shortName = underscore(shortName);
+
+
+ current[shortName] = Class;
+ }
+
+ // set things that can't be overwritten
+ $.extend(Class, {
+ prototype: prototype,
+ /**
+ * @attribute namespace
+ * The namespaces object
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.namespace //-> MyOrg
+ *
+ */
+ namespace: namespace,
+ /**
+ * @attribute shortName
+ * The name of the class without its namespace, provided for introspection purposes.
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.shortName //-> 'MyClass'
+ * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
+ *
+ */
+ shortName: shortName,
+ _shortName : _shortName,
+ _fullName: _fullName,
+ constructor: Class,
+ /**
+ * @attribute fullName
+ * The full name of the class, including namespace, provided for introspection purposes.
+ *
+ * $.Class("MyOrg.MyClass",{},{})
+ * MyOrg.MyClass.shortName //-> 'MyClass'
+ * MyOrg.MyClass.fullName //-> 'MyOrg.MyClass'
+ *
+ */
+ fullName: fullName
+ });
+
+ //make sure our prototype looks nice
+ Class.prototype.Class = Class.prototype.constructor = Class;
+
+
+
+ // call the class setup
+ var args = Class.setup.apply(Class, [_super_class].concat($.makeArray(arguments)) );
+
+ // call the class init
+ if ( Class.init ) {
+ Class.init.apply(Class, args || [_super_class].concat($.makeArray(arguments)) );
+ }
+
+ /* @Prototype*/
+ return Class;
+ /**
+ * @function setup
+ * If a setup method is provided, it is called when a new
+ * instances is created. It gets passed the same arguments that
+ * were given to the Class constructor function ( new Class( arguments ... )).
+ *
+ * $.Class("MyClass",
+ * {
+ * setup: function( val ) {
+ * this.val = val;
+ * }
+ * })
+ * var mc = new MyClass("Check Check")
+ * mc.val //-> 'Check Check'
+ *
+ * Setup is called before [jQuery.Class.prototype.init init]. If setup
+ * return an array, those arguments will be used for init.
+ *
+ * $.Class("jQuery.Controller",{
+ * setup : function(htmlElement, rawOptions){
+ * return [$(htmlElement),
+ * $.extend({}, this.Class.defaults, rawOptions )]
+ * }
+ * })
+ *
+ * init method is provided, it gets called when a new instance
+ * is created. Init gets called after [jQuery.Class.prototype.setup setup], typically with the
+ * same arguments passed to the Class
+ * constructor: ( new Class( arguments ... )).
+ *
+ * $.Class("MyClass",
+ * {
+ * init: function( val ) {
+ * this.val = val;
+ * }
+ * })
+ * var mc = new MyClass(1)
+ * mc.val //-> 1
+ *
+ * [jQuery.Class.prototype.setup Setup] is able to modify the arguments passed to init. Read
+ * about it there.
+ *
+ */
+ //Breaks up code
+ /**
+ * @attribute constructor
+ *
+ * A reference to the Class (or constructor function). This allows you to access
+ * a class's static properties from an instance.
+ *
+ * ### Quick Example
+ *
+ * // a class with a static property
+ * $.Class("MyClass", {staticProperty : true}, {});
+ *
+ * // a new instance of myClass
+ * var mc1 = new MyClass();
+ *
+ * // read the static property from the instance:
+ * mc1.constructor.staticProperty //-> true
+ *
+ * Getting static properties with the constructor property, like
+ * [jQuery.Class.static.fullName fullName], is very common.
+ *
+ */
+ }
+
+ });
+
+
+
+
+
+})(jQuery);
+(function() {
+
+ // Alias helpful methods from jQuery
+ var isObject = function( obj ) {
+ return typeof obj === 'object' && obj !== null && obj && !(obj instanceof Date);
+ },
+ // listens to changes on val and 'bubbles' the event up
+ // - val the object to listen to changes on
+ // - prop the property name val is at on
+ // - parent the parent object of prop
+ hookup = function( val, prop, parent ) {
+ // if it's an array make a list, otherwise a val
+ if (val instanceof $Observe){
+ // we have an observe already
+ // make sure it is not listening to this already
+ unhookup([val], parent._namespace)
+ } else if ( $.isArray(val) ) {
+ val = new $Observe.List(val)
+ } else {
+ val = new $Observe(val)
+ }
+ // attr (like target, how you (delegate) to get to the target)
+ // currentAttr (how to get to you)
+ // delegateAttr (hot to get to the delegated Attr)
+
+ //
+ //
+ //listen to all changes and trigger upwards
+ val.bind("change" + parent._namespace, function( ev, attr ) {
+ // trigger the type on this ...
+ var args = $.makeArray(arguments),
+ ev = args.shift();
+ if(prop === "*"){
+ args[0] = parent.indexOf(val)+"." + args[0]
+ } else {
+ args[0] = prop + "." + args[0]
+ }
+ $.event.trigger(ev, args, parent, true)
+ });
+
+ return val;
+ },
+ unhookup = function(items, namespace){
+ return $.each(items, function(i, item){
+ if(item && item.unbind){
+ item.unbind("change" + namespace)
+ }
+ });
+ },
+ // an id to track events for a given observe
+ id = 0,
+ collecting = null,
+ // call to start collecting events (Observe sends all events at once)
+ collect = function() {
+ if (!collecting ) {
+ collecting = [];
+ return true;
+ }
+ },
+ // creates an event on item, but will not send immediately
+ // if collecting events
+ // - item - the item the event should happen on
+ // - event - the event name ("change")
+ // - args - an array of arguments
+ trigger = function( item, event, args ) {
+ // send no events if initalizing
+ if (item._init) {
+ return;
+ }
+ if (!collecting ) {
+ return $.event.trigger(event, args, item, true);
+ } else {
+ collecting.push([{
+ type: event,
+ batchNum : batchNum
+ }, args, item, true ] );
+ }
+ },
+ // which batch of events this is for, might not want to send multiple
+ // messages on the same batch. This is mostly for
+ // event delegation
+ batchNum = 1,
+ // sends all pending events
+ sendCollection = function() {
+ var len = collecting.length,
+ items = collecting.slice(0),
+ cur;
+ collecting = null;
+ batchNum ++;
+ for ( var i = 0; i < len; i++ ) {
+ $.event.trigger.apply($.event, items[i]);
+ }
+
+ },
+ // a helper used to serialize an Observe or Observe.List where:
+ // observe - the observable
+ // how - to serialize with 'attr' or 'serialize'
+ // where - to put properties, in a {} or [].
+ serialize = function( observe, how, where ) {
+ // go through each property
+ observe.each(function( name, val ) {
+ // if the value is an object, and has a attrs or serialize function
+ where[name] = isObject(val) && typeof val[how] == 'function' ?
+ // call attrs or serialize to get the original data back
+ val[how]() :
+ // otherwise return the value
+ val
+ })
+ return where;
+ },
+ $method = function( name ) {
+ return function( eventType, handler ) {
+ return $.fn[name].apply($([this]), arguments);
+ }
+ },
+ bind = $method('bind'),
+ unbind = $method('unbind');
+
+ /**
+ * @class jQuery.Observe
+ * @parent jquerymx.lang
+ * @test jquery/lang/observe/qunit.html
+ *
+ * Observe provides the awesome observable pattern for
+ * JavaScript Objects and Arrays. It lets you
+ *
+ * - Set and remove property or property values on objects and arrays
+ * - Listen for changes in objects and arrays
+ * - Work with nested properties
+ *
+ * ## Creating an $.Observe
+ *
+ * To create an $.Observe, or $.Observe.List, you can simply use
+ * the `$.O(data)` shortcut like:
+ *
+ * var person = $.O({name: 'justin', age: 29}),
+ * hobbies = $.O(['programming', 'basketball', 'nose picking'])
+ *
+ * Depending on the type of data passed to $.O, it will create an instance of either:
+ *
+ * - $.Observe, which is used for objects like: `{foo: 'bar'}`, and
+ * - [jQuery.Observe.List $.Observe.List], which is used for arrays like `['foo','bar']`
+ *
+ * $.Observe.List and $.Observe are very similar. In fact,
+ * $.Observe.List inherits $.Observe and only adds a few extra methods for
+ * manipulating arrays like [jQuery.Observe.List.prototype.push push]. Go to
+ * [jQuery.Observe.List $.Observe.List] for more information about $.Observe.List.
+ *
+ * You can also create a `new $.Observe` simply by pass it the data you want to observe:
+ *
+ * var data = {
+ * addresses : [
+ * {
+ * city: 'Chicago',
+ * state: 'IL'
+ * },
+ * {
+ * city: 'Boston',
+ * state : 'MA'
+ * }
+ * ],
+ * name : "Justin Meyer"
+ * },
+ * o = new $.Observe(data);
+ *
+ * _o_ now represents an observable copy of _data_.
+ *
+ * ## Getting and Setting Properties
+ *
+ * Use [jQuery.Observe.prototype.attr attr] and [jQuery.Observe.prototype.attr attrs]
+ * to get and set properties.
+ *
+ * For example, you can read the property values of _o_ with
+ * `observe.attr( name )` like:
+ *
+ * // read name
+ * o.attr('name') //-> Justin Meyer
+ *
+ * And set property names of _o_ with
+ * `observe.attr( name, value )` like:
+ *
+ * // update name
+ * o.attr('name', "Brian Moschel") //-> o
+ *
+ * Observe handles nested data. Nested Objects and
+ * Arrays are converted to $.Observe and
+ * $.Observe.Lists. This lets you read nested properties
+ * and use $.Observe methods on them. The following
+ * updates the second address (Boston) to 'New York':
+ *
+ * o.attr('addresses.1').attr({
+ * city: 'New York',
+ * state: 'NY'
+ * })
+ *
+ * `attr()` can be used to get all properties back from the observe:
+ *
+ * o.attr() // ->
+ * {
+ * addresses : [
+ * {
+ * city: 'Chicago',
+ * state: 'IL'
+ * },
+ * {
+ * city: 'New York',
+ * state : 'MA'
+ * }
+ * ],
+ * name : "Brian Moschel"
+ * }
+ *
+ * ## Listening to property changes
+ *
+ * When a property value is changed, it creates events
+ * that you can listen to. There are two ways to listen
+ * for events:
+ *
+ * - [jQuery.Observe.prototype.bind bind] - listen for any type of change
+ * - [jQuery.Observe.prototype.delegate delegate] - listen to a specific type of change
+ *
+ * With `bind( "change" , handler( ev, attr, how, newVal, oldVal ) )`, you can listen
+ * to any change that happens within the
+ * observe. The handler gets called with the property name that was
+ * changed, how it was changed ['add','remove','set'], the new value
+ * and the old value.
+ *
+ * o.bind('change', function( ev, attr, how, nevVal, oldVal ) {
+ *
+ * })
+ *
+ * `delegate( attr, event, handler(ev, newVal, oldVal ) )` lets you listen
+ * to a specific event on a specific attribute.
+ *
+ * // listen for name changes
+ * o.delegate("name","set", function(){
+ *
+ * })
+ *
+ * Delegate lets you specify multiple attributes and values to match
+ * for the callback. For example,
+ *
+ * r = $.O({type: "video", id : 5})
+ * r.delegate("type=images id","set", function(){})
+ *
+ * This is used heavily by [jQuery.route $.route].
+ *
+ * @constructor
+ *
+ * @param {Object} obj a JavaScript Object that will be
+ * converted to an observable
+ */
+ var count = 0,
+ $Observe = $.Class('jQuery.Observe',{
+ // keep so it can be overwritten
+ setup : function(baseClass){
+ $.Class.setup.apply(this, arguments)
+ },
+ bind : bind,
+ unbind: unbind
+ },
+ /**
+ * @prototype
+ */
+ {
+ init: function( obj ) {
+ // _data is where we keep the properties
+ this._data = {};
+ // the namespace this object uses to listen to events
+ this._namespace = ".observe" + (++id);
+ // sets all attrs
+ this._init = 1;
+ this.attr(obj);
+ delete this._init;
+ },
+ /**
+ * Get or set an attribute on the observe.
+ *
+ * o = new $.Observe({});
+ *
+ * // sets a user property
+ * o.attr('user',{name: 'hank'});
+ *
+ * // read the user's name
+ * o.attr('user.name') //-> 'hank'
+ *
+ * If a value is set for the first time, it will trigger
+ * an `'add'` and `'set'` change event. Once
+ * the value has been added. Any future value changes will
+ * trigger only `'set'` events.
+ *
+ *
+ * @param {String} attr the attribute to read or write.
+ *
+ * o.attr('name') //-> reads the name
+ * o.attr('name', 'Justin') //-> writes the name
+ *
+ * You can read or write deep property names. For example:
+ *
+ * o.attr('person', {name: 'Justin'})
+ * o.attr('person.name') //-> 'Justin'
+ *
+ * @param {Object} [val] if provided, sets the value.
+ * @return {Object} the observable or the attribute property.
+ *
+ * If you are reading, the property value is returned:
+ *
+ * o.attr('name') //-> Justin
+ *
+ * If you are writing, the observe is returned for chaining:
+ *
+ * o.attr('name',"Brian").attr('name') //-> Justin
+ */
+ attr: function( attr, val ) {
+ var tAttr= typeof attr;
+ if(tAttr != 'string' && tAttr != 'number'){
+ return this._attrs(attr, val)
+ }else if ( val === undefined ) {
+ // if we are getting a value
+ return this._get(attr)
+ } else {
+ // otherwise we are setting
+ this._set(attr, val);
+ return this;
+ }
+ },
+ /**
+ * Iterates through each attribute, calling handler
+ * with each attribute name and value.
+ *
+ * new Observe({foo: 'bar'})
+ * .each(function(name, value){
+ * equals(name, 'foo')
+ * equals(value,'bar')
+ * })
+ *
+ * @param {function} handler(attrName,value) A function that will get
+ * called back with the name and value of each attribute on the observe.
+ *
+ * Returning `false` breaks the looping. The following will never
+ * log 3:
+ *
+ * new Observe({a : 1, b : 2, c: 3})
+ * .each(function(name, value){
+ * console.log(value)
+ * if(name == 2){
+ * return false;
+ * }
+ * })
+ *
+ * @return {jQuery.Observe} the original observable.
+ */
+ each: function() {
+ return $.each.apply(null, [this.__get()].concat($.makeArray(arguments)))
+ },
+ /**
+ * Removes a property
+ *
+ * o = new $.Observe({foo: 'bar'});
+ * o.removeAttr('foo'); //-> 'bar'
+ *
+ * This creates a `'remove'` change event. Learn more about events
+ * in [jQuery.Observe.prototype.bind bind] and [jQuery.Observe.prototype.delegate delegate].
+ *
+ * @param {String} attr the attribute name to remove.
+ * @return {Object} the value that was removed.
+ */
+ removeAttr: function( attr ) {
+ // convert the attr into parts (if nested)
+ var parts = $.isArray(attr) ? attr : attr.split("."),
+ // the actual property to remove
+ prop = parts.shift(),
+ // the current value
+ current = this._data[prop];
+
+ // if we have more parts, call removeAttr on that part
+ if ( parts.length ) {
+ return current.removeAttr(parts)
+ } else {
+ // otherwise, delete
+ delete this._data[prop];
+ // create the event
+ if (!(prop in this.constructor.prototype)) {
+ delete this[prop]
+ }
+ trigger(this, "change", [prop, "remove", undefined, current]);
+ return current;
+ }
+ },
+ // reads a property from the object
+ _get: function( attr ) {
+ var parts = $.isArray(attr) ? attr : (""+attr).split("."),
+ current = this.__get(parts.shift());
+ if ( parts.length ) {
+ return current ? current._get(parts) : undefined
+ } else {
+ return current;
+ }
+ },
+ // reads a property directly if an attr is provided, otherwise
+ // returns the 'real' data object itself
+ __get: function( attr ) {
+ return attr ? this._data[attr] : this._data;
+ },
+ // sets attr prop as value on this object where
+ // attr - is a string of properties or an array of property values
+ // value - the raw value to set
+ // description - an object with converters / attrs / defaults / getterSetters ?
+ _set: function( attr, value ) {
+ // convert attr to attr parts (if it isn't already)
+ var parts = $.isArray(attr) ? attr : ("" + attr).split("."),
+ // the immediate prop we are setting
+ prop = parts.shift(),
+ // its current value
+ current = this.__get(prop);
+
+ // if we have an object and remaining parts
+ if ( isObject(current) && parts.length ) {
+ // that object should set it (this might need to call attr)
+ current._set(parts, value)
+ } else if (!parts.length ) {
+ // we're in 'real' set territory
+
+ this.__set(prop, value, current)
+
+ } else {
+ throw "jQuery.Observe: set a property on an object that does not exist"
+ }
+ },
+ __set : function(prop, value, current){
+ // otherwise, we are setting it on this object
+ // todo: check if value is object and transform
+ // are we changing the value
+ if ( value !== current ) {
+
+ // check if we are adding this for the first time
+ // if we are, we need to create an 'add' event
+ var changeType = this.__get().hasOwnProperty(prop) ? "set" : "add";
+
+ // set the value on data
+ this.___set(prop,
+ // if we are getting an object
+ isObject(value) ?
+ // hook it up to send event to us
+ hookup(value, prop, this) :
+ // value is normal
+ value);
+
+
+
+ // trigger the change event
+ trigger(this, "change", [prop, changeType, value, current]);
+ trigger(this, prop, value, current);
+ // if we can stop listening to our old value, do it
+ current && unhookup([current], this._namespace);
+ }
+
+ },
+ // directly sets a property on this object
+ ___set: function( prop, val ) {
+ this._data[prop] = val;
+ // add property directly for easy writing
+ // check if its on the prototype so we don't overwrite methods like attrs
+ if (!(prop in this.constructor.prototype)) {
+ this[prop] = val
+ }
+ },
+ /**
+ * Listens to changes on a jQuery.Observe.
+ *
+ * When attributes of an observe change, including attributes on nested objects,
+ * a `'change'` event is triggered on the observe. These events come
+ * in three flavors:
+ *
+ * - `add` - a attribute is added
+ * - `set` - an existing attribute's value is changed
+ * - `remove` - an attribute is removed
+ *
+ * The change event is fired with:
+ *
+ * - the attribute changed
+ * - how it was changed
+ * - the newValue of the attribute
+ * - the oldValue of the attribute
+ *
+ * Example:
+ *
+ * o = new $.Observe({name : "Payal"});
+ * o.bind('change', function(ev, attr, how, newVal, oldVal){
+ * // ev -> {type: 'change'}
+ * // attr -> "name"
+ * // how -> "add"
+ * // newVal-> "Justin"
+ * // oldVal-> undefined
+ * })
+ *
+ * o.attr('name', 'Justin')
+ *
+ * Listening to `change` is only useful for when you want to
+ * know every change on an Observe. For most applications,
+ * [jQuery.Observe.prototype.delegate delegate] is
+ * much more useful as it lets you listen to specific attribute
+ * changes and sepecific types of changes.
+ *
+ *
+ * @param {String} eventType the event name. Currently,
+ * only 'change' events are supported. For more fine
+ * grained control, use [jQuery.Observe.prototype.delegate].
+ *
+ * @param {Function} handler(event, attr, how, newVal, oldVal) A
+ * callback function where
+ *
+ * - event - the event
+ * - attr - the name of the attribute changed
+ * - how - how the attribute was changed (add, set, remove)
+ * - newVal - the new value of the attribute
+ * - oldVal - the old value of the attribute
+ *
+ * @return {$.Observe} the observe for chaining.
+ */
+ bind: bind,
+ /**
+ * Unbinds a listener. This uses [http://api.jquery.com/unbind/ jQuery.unbind]
+ * and works very similar. This means you can
+ * use namespaces or unbind all event handlers for a given event:
+ *
+ * // unbind a specific event handler
+ * o.unbind('change', handler)
+ *
+ * // unbind all change event handlers bound with the
+ * // foo namespace
+ * o.unbind('change.foo')
+ *
+ * // unbind all change event handlers
+ * o.unbind('change')
+ *
+ * @param {String} eventType - the type of event with
+ * any optional namespaces. Currently, only `change` events
+ * are supported with bind.
+ *
+ * @param {Function} [handler] - The original handler function passed
+ * to [jQuery.Observe.prototype.bind bind].
+ *
+ * @return {jQuery.Observe} the original observe for chaining.
+ */
+ unbind: unbind,
+ /**
+ * Get the serialized Object form of the observe. Serialized
+ * data is typically used to send back to a server.
+ *
+ * o.serialize() //-> { name: 'Justin' }
+ *
+ * Serialize currently returns the same data
+ * as [jQuery.Observe.prototype.attrs]. However, in future
+ * versions, serialize will be able to return serialized
+ * data similar to [jQuery.Model]. The following will work:
+ *
+ * new Observe({time: new Date()})
+ * .serialize() //-> { time: 1319666613663 }
+ *
+ * @return {Object} a JavaScript Object that can be
+ * serialized with `JSON.stringify` or other methods.
+ *
+ */
+ serialize: function() {
+ return serialize(this, 'serialize', {});
+ },
+ /**
+ * Set multiple properties on the observable
+ * @param {Object} props
+ * @param {Boolean} remove true if you should remove properties that are not in props
+ */
+ _attrs: function( props, remove ) {
+ if ( props === undefined ) {
+ return serialize(this, 'attr', {})
+ }
+
+ props = $.extend(true, {}, props);
+ var prop,
+ collectingStarted = collect(),
+ self = this;
+
+ this.each(function(prop, curVal){
+ var newVal = props[prop];
+
+ // if we are merging ...
+ if ( newVal === undefined ) {
+ remove && self.removeAttr(prop);
+ return;
+ }
+ if ( isObject(curVal) && isObject(newVal) ) {
+ curVal.attr(newVal, remove)
+ } else if ( curVal != newVal ) {
+ self._set(prop, newVal)
+ } else {
+
+ }
+ delete props[prop];
+ })
+ // add remaining props
+ for ( var prop in props ) {
+ newVal = props[prop];
+ this._set(prop, newVal)
+ }
+ if ( collectingStarted ) {
+ sendCollection();
+ }
+ return this;
+ }
+ });
+ // Helpers for list
+ /**
+ * @class jQuery.Observe.List
+ * @inherits jQuery.Observe
+ * @parent jQuery.Observe
+ *
+ * An observable list. You can listen to when items are push, popped,
+ * spliced, shifted, and unshifted on this array.
+ *
+ *
+ */
+ var splice = [].splice,
+ list = $Observe('jQuery.Observe.List',
+ /**
+ * @prototype
+ */
+ {
+ init: function( instances, options ) {
+ this.length = 0;
+ this._namespace = ".list" + (++id);
+ this._init = 1;
+ this.bind('change',$.proxy(this._changes,this));
+ this.push.apply(this, $.makeArray(instances || []));
+ $.extend(this, options);
+ //if(this.comparator){
+ // this.sort()
+ //}
+ delete this._init;
+ },
+ _changes : function(ev, attr, how, newVal, oldVal){
+ // detects an add, sorts it, re-adds?
+ //console.log("")
+
+
+
+ // if we are sorting, and an attribute inside us changed
+ /*if(this.comparator && /^\d+./.test(attr) ) {
+
+ // get the index
+ var index = +/^\d+/.exec(attr)[0],
+ // and item
+ item = this[index],
+ // and the new item
+ newIndex = this.sortedIndex(item);
+
+ if(newIndex !== index){
+ // move ...
+ splice.call(this, index, 1);
+ splice.call(this, newIndex, 0, item);
+
+ trigger(this, "move", [item, newIndex, index]);
+ ev.stopImmediatePropagation();
+ trigger(this,"change", [
+ attr.replace(/^\d+/,newIndex),
+ how,
+ newVal,
+ oldVal
+ ]);
+ return;
+ }
+ }*/
+
+
+ // if we add items, we need to handle
+ // sorting and such
+
+ // trigger direct add and remove events ...
+ if(attr.indexOf('.') === -1){
+
+ if( how === 'add' ) {
+ trigger(this, how, [newVal,+attr]);
+ } else if( how === 'remove' ) {
+ trigger(this, how, [oldVal, +attr])
+ }
+
+ }
+ // issue add, remove, and move events ...
+ },
+ /*sortedIndex : function(item){
+ var itemCompare = item.attr(this.comparator),
+ equaled = 0,
+ i;
+ for(var i =0; i < this.length; i++){
+ if(item === this[i]){
+ equaled = -1;
+ continue;
+ }
+ if(itemCompare <= this[i].attr(this.comparator) ) {
+ return i+equaled;
+ }
+ }
+ return i+equaled;
+ },*/
+ __get : function(attr){
+ return attr ? this[attr] : this;
+ },
+ ___set : function(attr, val){
+ this[attr] = val;
+ },
+ /**
+ * Returns the serialized form of this list.
+ */
+ serialize: function() {
+ return serialize(this, 'serialize', []);
+ },
+ /**
+ * Iterates through each item of the list, calling handler
+ * with each index and value.
+ *
+ * new Observe.List(['a'])
+ * .each(function(index, value){
+ * equals(index, 1)
+ * equals(value,'a')
+ * })
+ *
+ * @param {function} handler(index,value) A function that will get
+ * called back with the index and value of each item on the list.
+ *
+ * Returning `false` breaks the looping. The following will never
+ * log 'c':
+ *
+ * new Observe(['a','b','c'])
+ * .each(function(index, value){
+ * console.log(value)
+ * if(index == 1){
+ * return false;
+ * }
+ * })
+ *
+ * @return {jQuery.Observe.List} the original observable.
+ */
+ // placeholder for each
+ /**
+ * Remove items or add items from a specific point in the list.
+ *
+ * ### Example
+ *
+ * The following creates a list of numbers and replaces 2 and 3 with
+ * "a", and "b".
+ *
+ * var l = new $.Observe.List([0,1,2,3]);
+ *
+ * l.bind('change', function( ev, attr, how, newVals, oldVals, where ) { ... })
+ *
+ * l.splice(1,2, "a", "b"); // results in [0,"a","b",3]
+ *
+ * This creates 2 change events. The first event is the removal of
+ * numbers one and two where it's callback values will be:
+ *
+ * - attr - "1" - indicates where the remove event took place
+ * - how - "remove"
+ * - newVals - undefined
+ * - oldVals - [1,2] -the array of removed values
+ * - where - 1 - the location of where these items where removed
+ *
+ * The second change event is the addition of the "a", and "b" values where
+ * the callback values will be:
+ *
+ * - attr - "1" - indicates where the add event took place
+ * - how - "added"
+ * - newVals - ["a","b"]
+ * - oldVals - [1, 2] - the array of removed values
+ * - where - 1 - the location of where these items where added
+ *
+ * @param {Number} index where to start removing or adding items
+ * @param {Object} count the number of items to remove
+ * @param {Object} [added] an object to add to
+ */
+ splice: function( index, count ) {
+ var args = $.makeArray(arguments),
+ i;
+
+ for ( i = 2; i < args.length; i++ ) {
+ var val = args[i];
+ if ( isObject(val) ) {
+ args[i] = hookup(val, "*", this)
+ }
+ }
+ if ( count === undefined ) {
+ count = args[1] = this.length - index;
+ }
+ var removed = splice.apply(this, args);
+ if ( count > 0 ) {
+ trigger(this, "change", [""+index, "remove", undefined, removed]);
+ unhookup(removed, this._namespace);
+ }
+ if ( args.length > 2 ) {
+ trigger(this, "change", [""+index, "add", args.slice(2), removed]);
+ }
+ return removed;
+ },
+ /**
+ * Updates an array with a new array. It is able to handle
+ * removes in the middle of the array.
+ *
+ * @param {Array} props
+ * @param {Boolean} remove
+ */
+ _attrs: function( props, remove ) {
+ if ( props === undefined ) {
+ return serialize(this, 'attr', []);
+ }
+
+ // copy
+ props = props.slice(0);
+
+ var len = Math.min(props.length, this.length),
+ collectingStarted = collect();
+ for ( var prop = 0; prop < len; prop++ ) {
+ var curVal = this[prop],
+ newVal = props[prop];
+
+ if ( isObject(curVal) && isObject(newVal) ) {
+ curVal.attr(newVal, remove)
+ } else if ( curVal != newVal ) {
+ this._set(prop, newVal)
+ } else {
+
+ }
+ }
+ if ( props.length > this.length ) {
+ // add in the remaining props
+ this.push(props.slice(this.length))
+ } else if ( props.length < this.length && remove ) {
+ this.splice(props.length)
+ }
+ //remove those props didn't get too
+ if ( collectingStarted ) {
+ sendCollection()
+ }
+ }
+ }),
+
+
+ // create push, pop, shift, and unshift
+ // converts to an array of arguments
+ getArgs = function( args ) {
+ if ( args[0] && ($.isArray(args[0])) ) {
+ return args[0]
+ }
+ else {
+ return $.makeArray(args)
+ }
+ };
+ // describes the method and where items should be added
+ $.each({
+ /**
+ * @function push
+ * Add items to the end of the list.
+ *
+ * var l = new $.Observe.List([]);
+ *
+ * l.bind('change', function(
+ * ev, // the change event
+ * attr, // the attr that was changed, for multiple items, "*" is used
+ * how, // "add"
+ * newVals, // an array of new values pushed
+ * oldVals, // undefined
+ * where // the location where these items where added
+ * ) {
+ *
+ * })
+ *
+ * l.push('0','1','2');
+ *
+ * @return {Number} the number of items in the array
+ */
+ push: "length",
+ /**
+ * @function unshift
+ * Add items to the start of the list. This is very similar to
+ * [jQuery.Observe.prototype.push].
+ */
+ unshift: 0
+ },
+ // adds a method where
+ // - name - method name
+ // - where - where items in the array should be added
+
+
+ function( name, where ) {
+ list.prototype[name] = function() {
+ // get the items being added
+ var args = getArgs(arguments),
+ // where we are going to add items
+ len = where ? this.length : 0;
+
+ // go through and convert anything to an observe that needs to be converted
+ for ( var i = 0; i < args.length; i++ ) {
+ var val = args[i];
+ if ( isObject(val) ) {
+ args[i] = hookup(val, "*", this)
+ }
+ }
+
+ // if we have a sort item, add that
+ if( args.length == 1 && this.comparator ) {
+ // add each item ...
+ // we could make this check if we are already adding in order
+ // but that would be confusing ...
+ var index = this.sortedIndex(args[0]);
+ this.splice(index, 0, args[0]);
+ return this.length;
+ }
+
+ // call the original method
+ var res = [][name].apply(this, args)
+
+ // cause the change where the args are:
+ // len - where the additions happened
+ // add - items added
+ // args - the items added
+ // undefined - the old value
+ if ( this.comparator && args.length > 1) {
+ this.sort(null, true);
+ trigger(this,"reset", [args])
+ } else {
+ trigger(this, "change", [""+len, "add", args, undefined])
+ }
+
+
+ return res;
+ }
+ });
+
+ $.each({
+ /**
+ * @function pop
+ *
+ * Removes an item from the end of the list.
+ *
+ * var l = new $.Observe.List([0,1,2]);
+ *
+ * l.bind('change', function(
+ * ev, // the change event
+ * attr, // the attr that was changed, for multiple items, "*" is used
+ * how, // "remove"
+ * newVals, // undefined
+ * oldVals, // 2
+ * where // the location where these items where added
+ * ) {
+ *
+ * })
+ *
+ * l.pop();
+ *
+ * @return {Object} the element at the end of the list
+ */
+ pop: "length",
+ /**
+ * @function shift
+ * Removes an item from the start of the list. This is very similar to
+ * [jQuery.Observe.prototype.pop].
+ *
+ * @return {Object} the element at the start of the list
+ */
+ shift: 0
+ },
+ // creates a 'remove' type method
+
+
+ function( name, where ) {
+ list.prototype[name] = function() {
+
+ var args = getArgs(arguments),
+ len = where && this.length ? this.length - 1 : 0;
+
+
+ var res = [][name].apply(this, args)
+
+ // create a change where the args are
+ // "*" - change on potentially multiple properties
+ // "remove" - items removed
+ // undefined - the new values (there are none)
+ // res - the old, removed values (should these be unbound)
+ // len - where these items were removed
+ trigger(this, "change", [""+len, "remove", undefined, [res]])
+
+ if ( res && res.unbind ) {
+ res.unbind("change" + this._namespace)
+ }
+ return res;
+ }
+ });
+
+ list.prototype.
+ /**
+ * @function indexOf
+ * Returns the position of the item in the array. Returns -1 if the
+ * item is not in the array.
+ * @param {Object} item
+ * @return {Number}
+ */
+ indexOf = [].indexOf || function(item){
+ return $.inArray(item, this)
+ }
+
+ /**
+ * @class $.O
+ */
+ $.O = function(data, options){
+ if($.isArray(data) || data instanceof $Observe.List){
+ return new $Observe.List(data, options)
+ } else {
+ return new $Observe(data, options)
+ }
+ }
+})(jQuery);
+(function($){
+ '$:nomunge'; // Used by YUI compressor.
+
+ // Method / object references.
+ var fake_onhashchange,
+ jq_event_special = $.event.special,
+
+ // Reused strings.
+ str_location = 'location',
+ str_hashchange = 'hashchange',
+ str_href = 'href',
+
+ // IE6/7 specifically need some special love when it comes to back-button
+ // support, so let's do a little browser sniffing..
+ browser = $.browser,
+ mode = document.documentMode,
+ is_old_ie = browser.msie && ( mode === undefined || mode < 8 ),
+
+ // Does the browser support window.onhashchange? Test for IE version, since
+ // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
+ supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
+
+ // Get location.hash (or what you'd expect location.hash to be) sans any
+ // leading #. Thanks for making this necessary, Firefox!
+ function get_fragment( url ) {
+ url = url || window[ str_location ][ str_href ];
+ return url.replace( /^[^#]*#?(.*)$/, '$1' );
+ };
+
+ // Property: jQuery.hashchangeDelay
+ //
+ // The numeric interval (in milliseconds) at which the "foo=bar&person[age]=3"
+ * @return {Object} A JavaScript Object that represents the params:
+ *
+ * {
+ * foo: "bar",
+ * person: {
+ * age: "3"
+ * }
+ * }
+ */
+ deparam: function(params){
+
+ if(! params || ! paramTest.test(params) ) {
+ return {};
+ }
+
+
+ var data = {},
+ pairs = params.split('&'),
+ current;
+
+ for(var i=0; i < pairs.length; i++){
+ current = data;
+ var pair = pairs[i].split('=');
+
+ // if we find foo=1+1=2
+ if(pair.length != 2) {
+ pair = [pair[0], pair.slice(1).join("=")]
+ }
+
+ var key = decodeURIComponent(pair[0].replace(plus, " ")),
+ value = decodeURIComponent(pair[1].replace(plus, " ")),
+ parts = key.match(keyBreaker);
+
+ for ( var j = 0; j < parts.length - 1; j++ ) {
+ var part = parts[j];
+ if (!current[part] ) {
+ // if what we are pointing to looks like an array
+ current[part] = digitTest.test(parts[j+1]) || parts[j+1] == "[]" ? [] : {}
+ }
+ current = current[part];
+ }
+ lastPart = parts[parts.length - 1];
+ if(lastPart == "[]"){
+ current.push(value)
+ }else{
+ current[lastPart] = value;
+ }
+ }
+ return data;
+ }
+ });
+
+})(jQuery);
+(function( $ ) {
+ /**
+ * @attribute destroyed
+ * @parent specialevents
+ * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/dom/destroyed/destroyed.js
+ * @test jquery/event/destroyed/qunit.html
+ * Provides a destroyed event on an element.
+ * + * The destroyed event is called when the element + * is removed as a result of jQuery DOM manipulators like remove, html, + * replaceWith, etc. Destroyed events do not bubble, so make sure you don't use live or delegate with destroyed + * events. + *
+ *window.location.hash. By
+ * changing the hash (via a link or JavaScript),
+ * one is able to add to the browser's history
+ * without changing the page. The [jQuery.event.special.hashchange event] allows
+ * you to listen to when the hash is changed.
+ *
+ * Combined, this provides the basics needed to
+ * create history enabled Ajax websites. However,
+ * jQuery.Route addresses several other needs such as:
+ *
+ * - Pretty Routes
+ * - Keeping routes independent of application code
+ * - Listening to specific parts of the history changing
+ * - Setup / Teardown of widgets.
+ *
+ * ## How it works
+ *
+ * $.route is a [jQuery.Observe $.Observe] that represents the
+ * window.location.hash as an
+ * object. For example, if the hash looks like:
+ *
+ * #!type=videos&id=5
+ *
+ * the data in $.route would look like:
+ *
+ * { type: 'videos', id: 5 }
+ *
+ *
+ * $.route keeps the state of the hash in-sync with the data in
+ * $.route.
+ *
+ * ## $.Observe
+ *
+ * $.route is a [jQuery.Observe $.Observe]. Understanding
+ * $.Observe is essential for using $.route correctly.
+ *
+ * You can
+ * listen to changes in an Observe with bind and
+ * delegate and change $.route's properties with
+ * attr and attrs.
+ *
+ * ### Listening to changes in an Observable
+ *
+ * Listen to changes in history
+ * by [jQuery.Observe.prototype.bind bind]ing to
+ * changes in $.route like:
+ *
+ * $.route.bind('change', function(ev, attr, how, newVal, oldVal) {
+ *
+ * })
+ *
+ * - attr - the name of the changed attribute
+ * - how - the type of Observe change event (add, set or remove)
+ * - newVal/oldVal - the new and old values of the attribute
+ *
+ * You can also listen to specific changes
+ * with [jQuery.Observe.prototype.delegate delegate]:
+ *
+ * $.route.delegate('id','change', function(){ ... })
+ *
+ * Observe lets you listen to the following events:
+ *
+ * - change - any change to the object
+ * - add - a property is added
+ * - set - a property value is added or changed
+ * - remove - a property is removed
+ *
+ * Listening for add is useful for widget setup
+ * behavior, remove is useful for teardown.
+ *
+ * ### Updating an observable
+ *
+ * Create changes in the route data like:
+ *
+ * $.route.attr('type','images');
+ *
+ * Or change multiple properties at once with
+ * [jQuery.Observe.prototype.attrs attrs]:
+ *
+ * $.route.attr({type: 'pages', id: 5}, true)
+ *
+ * When you make changes to $.route, they will automatically
+ * change the hash.
+ *
+ * ## Creating a Route
+ *
+ * Use $.route(url, defaults) to create a
+ * route. A route is a mapping from a url to
+ * an object (that is the $.route's state).
+ *
+ * If no routes are added, or no route is matched,
+ * $.route's data is updated with the [jQuery.String.deparam deparamed]
+ * hash.
+ *
+ * location.hash = "#!type=videos";
+ * // $.route -> {type : "videos"}
+ *
+ * Once routes are added and the hash changes,
+ * $.route looks for matching routes and uses them
+ * to update $.route's data.
+ *
+ * $.route( "content/:type" );
+ * location.hash = "#!content/images";
+ * // $.route -> {type : "images"}
+ *
+ * Default values can also be added:
+ *
+ * $.route("content/:type",{type: "videos" });
+ * location.hash = "#!content/"
+ * // $.route -> {type : "videos"}
+ *
+ * ## Delay setting $.route
+ *
+ * By default, $.route sets its initial data
+ * on document ready. Sometimes, you want to wait to set
+ * this data. To wait, call:
+ *
+ * $.route.ready(false);
+ *
+ * and when ready, call:
+ *
+ * $.route.ready(true);
+ *
+ * ## Changing the route.
+ *
+ * Typically, you never want to set location.hash
+ * directly. Instead, you can change properties on $.route
+ * like:
+ *
+ * $.route.attr('type', 'videos')
+ *
+ * This will automatically look up the appropriate
+ * route and update the hash.
+ *
+ * Often, you want to create links. $.route provides
+ * the [jQuery.route.link] and [jQuery.route.url] helpers to make this
+ * easy:
+ *
+ * $.route.link("Videos", {type: 'videos'})
+ *
+ * @param {String} url the fragment identifier to match.
+ * @param {Object} [defaults] an object of default values
+ * @return {jQuery.route}
+ */
+ $.route = function( url, defaults ) {
+ // Extract the variable names and replace with regEx that will match an atual URL with values.
+ var names = [],
+ test = url.replace(matcher, function( whole, name ) {
+ names.push(name)
+ return "([^\\/\\&]*)" // The '\\' is for string-escaping giving single '\' for regEx escaping
+ });
+
+ // Add route in a form that can be easily figured out
+ $.route.routes[url] = {
+ // A regular expression that will match the route when variable values
+ // are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which
+ // will match for any value of :page and :type (word chars or period).
+ test: new RegExp("^" + test),
+ // The original URL, same as the index for this entry in routes.
+ route: url,
+ // An array of all the variable names in this route
+ names: names,
+ // Default values provided for the variables.
+ defaults: defaults || {},
+ // The number of parts in the URL separated by '/'.
+ length: url.split('/').length
+ }
+ return $.route;
+ };
+
+ extend($.route, {
+ /**
+ * Parameterizes the raw JS object representation provided in data.
+ * If a route matching the provided data is found that URL is built
+ * from the data. Any remaining data is added at the end of the
+ * URL as & separated key/value parameters.
+ *
+ * @param {Object} data
+ * @return {String} The route URL and & separated parameters.
+ */
+ param: function( data ) {
+ // Check if the provided data keys match the names in any routes;
+ // get the one with the most matches.
+ delete data.route;
+ var route,
+ matches = -1,
+ matchCount;
+ each($.route.routes, function(name, temp){
+ matchCount = matchesData(temp, data);
+ if ( matchCount > matches ) {
+ route = temp;
+ matches = matchCount
+ }
+ });
+
+ if ( route ) {
+ var cpy = extend({}, data),
+ // Create the url by replacing the var names with the provided data.
+ // If the default value is found an empty string is inserted.
+ res = route.route.replace(matcher, function( whole, name ) {
+ delete cpy[name];
+ return data[name] === route.defaults[name] ? "" : encode( data[name] );
+ }),
+ after;
+ // remove matching default values
+ each(route.defaults, function(name,val){
+ if(cpy[name] === val) {
+ delete cpy[name]
+ }
+ })
+
+ // The remaining elements of data are added as
+ // $amp; separated parameters to the url.
+ after = $.param(cpy);
+ return res + (after ? "&" + after : "")
+ }
+ // If no route was found there is no hash URL, only paramters.
+ return $.isEmptyObject(data) ? "" : "&" + $.param(data);
+ },
+ /**
+ * Populate the JS data object from a given URL.
+ *
+ * @param {Object} url
+ */
+ deparam: function( url ) {
+ // See if the url matches any routes by testing it against the route.test regEx.
+ // By comparing the URL length the most specialized route that matches is used.
+ var route = {
+ length: -1
+ };
+ each($.route.routes, function(name, temp){
+ if ( temp.test.test(url) && temp.length > route.length ) {
+ route = temp;
+ }
+ });
+ // If a route was matched
+ if ( route.length > -1 ) {
+ var // Since RegEx backreferences are used in route.test (round brackets)
+ // the parts will contain the full matched string and each variable (backreferenced) value.
+ parts = url.match(route.test),
+ // start will contain the full matched string; parts contain the variable values.
+ start = parts.shift(),
+ // The remainder will be the &key=value list at the end of the URL.
+ remainder = url.substr(start.length),
+ // If there is a remainder and it contains a &key=value list deparam it.
+ obj = (remainder && paramsMatcher.test(remainder)) ? $.String.deparam( remainder.slice(1) ) : {};
+
+ // Add the default values for this route
+ obj = extend(true, {}, route.defaults, obj);
+ // Overwrite each of the default values in obj with those in parts if that part is not empty.
+ each(parts,function(i, part){
+ if ( part ) {
+ obj[route.names[i]] = decode( part );
+ }
+ });
+ obj.route = route.route;
+ return obj;
+ }
+ // If no route was matched it is parsed as a &key=value list.
+ return paramsMatcher.test(url) ? $.String.deparam( url.slice(1) ) : {};
+ },
+ /**
+ * @hide
+ * A $.Observe that represents the state of the history.
+ */
+ data: new $.Observe({}),
+ /**
+ * @attribute
+ * @type Object
+ * @hide
+ *
+ * A list of routes recognized by the router indixed by the url used to add it.
+ * Each route is an object with these members:
+ *
+ * - test - A regular expression that will match the route when variable values
+ * are present; i.e. for :page/:type the regEx is /([\w\.]*)/([\w\.]*)/ which
+ * will match for any value of :page and :type (word chars or period).
+ *
+ * - route - The original URL, same as the index for this entry in routes.
+ *
+ * - names - An array of all the variable names in this route
+ *
+ * - defaults - Default values provided for the variables or an empty object.
+ *
+ * - length - The number of parts in the URL separated by '/'.
+ */
+ routes: {},
+ /**
+ * Indicates that all routes have been added and sets $.route.data
+ * based upon the routes and the current hash.
+ * @param {Boolean} [start]
+ * @return
+ */
+ ready: function(val) {
+ if( val === false ) {
+ onready = false;
+ }
+ if( val === true || onready === true ) {
+ setState();
+ }
+ return $.route;
+ },
+ /**
+ * Returns a url from the options
+ * @param {Object} options
+ * @param {Boolean} merge true if the options should be merged with the current options
+ * @return {String}
+ */
+ url: function( options, merge ) {
+ if (merge) {
+ return "#!" + $.route.param(extend({}, curParams, options))
+ } else {
+ return "#!" + $.route.param(options)
+ }
+ },
+ /**
+ * Returns a link
+ * @param {Object} name The text of the link.
+ * @param {Object} options The route options (variables)
+ * @param {Object} props Properties of the <a> other than href.
+ * @param {Boolean} merge true if the options should be merged with the current options
+ */
+ link: function( name, options, props, merge ) {
+ return "" + name + "";
+ },
+ /**
+ * Returns true if the options represent the current page.
+ * @param {Object} options
+ * @return {Boolean}
+ */
+ current: function( options ) {
+ return location.hash == "#!" + $.route.param(options)
+ }
+ });
+ // onready
+ $(function() {
+ $.route.ready();
+ });
+
+ // The functions in the following list applied to $.route (e.g. $.route.attr('...')) will
+ // instead act on the $.route.data Observe.
+ each(['bind','unbind','delegate','undelegate','attr','serialize','removeAttr'], function(i, name){
+ $.route[name] = function(){
+ return $.route.data[name].apply($.route.data, arguments)
+ }
+ })
+
+ var // A throttled function called multiple times will only fire once the
+ // timer runs down. Each call resets the timer.
+ throttle = function( func ) {
+ var timer;
+ return function() {
+ clearTimeout(timer);
+ timer = setTimeout(func, 1);
+ }
+ },
+ // Intermediate storage for $.route.data.
+ curParams,
+ // Deparameterizes the portion of the hash of interest and assign the
+ // values to the $.route.data removing existing values no longer in the hash.
+ setState = function() {
+ var hash = location.hash.substr(1, 1) === '!' ?
+ location.hash.slice(2) :
+ location.hash.slice(1); // everything after #!
+ curParams = $.route.deparam( hash );
+ $.route.attr(curParams, true);
+ };
+
+ // If the hash changes, update the $.route.data
+ $(window).bind('hashchange', setState);
+
+ // If the $.route.data changes, update the hash.
+ // Using .serialize() retrieves the raw data contained in the observable.
+ // This function is throttled so it only updates once even if multiple values changed.
+ $.route.bind("change", throttle(function() {
+ location.hash = "#!" + $.route.param($.route.serialize())
+ }));
+})(jQuery);
+(function( $ ) {
+ // ------- HELPER FUNCTIONS ------
+
+ // Binds an element, returns a function that unbinds
+ var bind = function( el, ev, callback ) {
+ var binder = el.bind && el.unbind ? el : $(isFunction(el) ? [el] : el);
+
+ binder.bind(ev, callback);
+ // if ev name has >, change the name and bind
+ // in the wrapped callback, check that the element matches the actual element
+ return function() {
+ binder.unbind(ev, callback);
+ el = ev = callback = null;
+ };
+ },
+ isFunction = $.isFunction,
+ extend = $.extend,
+ each = $.each,
+ slice = [].slice,
+
+ // Binds an element, returns a function that unbinds
+ delegate = function( el, selector, ev, callback ) {
+ var binder = el.delegate && el.undelegate ? el : $(isFunction(el) ? [el] : el)
+ binder.delegate(selector, ev, callback);
+ return function() {
+ binder.undelegate(selector, ev, callback);
+ binder = el = ev = callback = selector = null;
+ };
+ },
+
+ // calls bind or unbind depending if there is a selector
+ binder = function( el, ev, callback, selector ) {
+ return selector ? delegate(el, selector, ev, callback) : bind(el, ev, callback);
+ },
+
+ // moves 'this' to the first argument, wraps it with jQuery if it's an element
+ shifter = function shifter(context, name) {
+ var method = typeof name == "string" ? context[name] : name;
+ return function() {
+ context.called = name;
+ return method.apply(context, [this.nodeName ? $(this) : this].concat( slice.call(arguments, 0) ) );
+ };
+ },
+ // matches dots
+ // checks if it looks like an action
+ actionMatcher = /[^\w]/,
+ // handles parameterized action names
+ parameterReplacer = /\{([^\}]+)\}/g,
+ breaker = /^(?:(.*?)\s)?([\w\.\:>]+)$/,
+ basicProcessor,
+ data = function(el, data){
+ return $.data(el, "controllers", data)
+ };
+ /**
+ * @class jQuery.Controller
+ * @parent jquerymx
+ * @plugin jquery/controller
+ * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/controller/controller.js
+ * @test jquery/controller/qunit.html
+ * @inherits jQuery.Class
+ * @description jQuery widget factory.
+ *
+ * jQuery.Controller helps create organized, memory-leak free, rapidly performing
+ * jQuery widgets. Its extreme flexibility allows it to serve as both
+ * a traditional View and a traditional Controller.
+ *
+ * This means it is used to
+ * create things like tabs, grids, and contextmenus as well as
+ * organizing them into higher-order business rules.
+ *
+ * Controllers make your code deterministic, reusable, organized and can tear themselves
+ * down auto-magically. Read about [http://jupiterjs.com/news/writing-the-perfect-jquery-plugin
+ * the theory behind controller] and
+ * a [http://jupiterjs.com/news/organize-jquery-widgets-with-jquery-controller walkthrough of its features]
+ * on Jupiter's blog. [mvc.controller Get Started with jQueryMX] also has a great walkthrough.
+ *
+ * Controller inherits from [jQuery.Class $.Class] and makes heavy use of
+ * [http://api.jquery.com/delegate/ event delegation]. Make sure
+ * you understand these concepts before using it.
+ *
+ * ## Basic Example
+ *
+ * Instead of
+ *
+ *
+ * $(function(){
+ * $('#tabs').click(someCallbackFunction1)
+ * $('#tabs .tab').click(someCallbackFunction2)
+ * $('#tabs .delete click').click(someCallbackFunction3)
+ * });
+ *
+ * do this
+ *
+ * $.Controller('Tabs',{
+ * click: function() {...},
+ * '.tab click' : function() {...},
+ * '.delete click' : function() {...}
+ * })
+ * $('#tabs').tabs();
+ *
+ *
+ * ## Tabs Example
+ *
+ * @demo jquery/controller/controller.html
+ *
+ * ## Using Controller
+ *
+ * Controller helps you build and organize jQuery plugins. It can be used
+ * to build simple widgets, like a slider, or organize multiple
+ * widgets into something greater.
+ *
+ * To understand how to use Controller, you need to understand
+ * the typical lifecycle of a jQuery widget and how that maps to
+ * controller's functionality:
+ *
+ * ### A controller class is created.
+ *
+ * $.Controller("MyWidget",
+ * {
+ * defaults : {
+ * message : "Remove Me"
+ * }
+ * },
+ * {
+ * init : function(rawEl, rawOptions){
+ * this.element.append(
+ * "$.fn.my_widget jQuery helper function
+ * that can be used to create a new controller instance on an element. Find
+ * more information [jquery.controller.plugin here] about the plugin gets created
+ * and the rules around its name.
+ *
+ * ### An instance of controller is created on an element
+ *
+ * $('.thing').my_widget(options) // calls new MyWidget(el, options)
+ *
+ * This calls new MyWidget(el, options) on
+ * each '.thing' element.
+ *
+ * When a new [jQuery.Class Class] instance is created, it calls the class's
+ * prototype setup and init methods. Controller's [jQuery.Controller.prototype.setup setup]
+ * method:
+ *
+ * - Sets [jQuery.Controller.prototype.element this.element] and adds the controller's name to element's className.
+ * - Merges passed in options with defaults object and sets it as [jQuery.Controller.prototype.options this.options]
+ * - Saves a reference to the controller in $.data.
+ * - [jquery.controller.listening Binds all event handler methods].
+ *
+ *
+ * ### The controller responds to events
+ *
+ * Typically, Controller event handlers are automatically bound. However, there are
+ * multiple ways to [jquery.controller.listening listen to events] with a controller.
+ *
+ * Once an event does happen, the callback function is always called with 'this'
+ * referencing the controller instance. This makes it easy to use helper functions and
+ * save state on the controller.
+ *
+ *
+ * ### The widget is destroyed
+ *
+ * If the element is removed from the page, the
+ * controller's [jQuery.Controller.prototype.destroy] method is called.
+ * This is a great place to put any additional teardown functionality.
+ *
+ * You can also teardown a controller programatically like:
+ *
+ * $('.thing').my_widget('destroy');
+ *
+ * ## Todos Example
+ *
+ * Lets look at a very basic example -
+ * a list of todos and a button you want to click to create a new todo.
+ * Your HTML might look like:
+ *
+ * @codestart html
+ * <div id='todos'>
+ * <ol>
+ * <li class="todo">Laundry</li>
+ * <li class="todo">Dishes</li>
+ * <li class="todo">Walk Dog</li>
+ * </ol>
+ * <a class="create">Create</a>
+ * </div>
+ * @codeend
+ *
+ * To add a mousover effect and create todos, your controller might look like:
+ *
+ * $.Controller('Todos',{
+ * ".todo mouseover" : function( el, ev ) {
+ * el.css("backgroundColor","red")
+ * },
+ * ".todo mouseout" : function( el, ev ) {
+ * el.css("backgroundColor","")
+ * },
+ * ".create click" : function() {
+ * this.find("ol").append("<li class='todo'>New Todo</li>");
+ * }
+ * })
+ *
+ * Now that you've created the controller class, you've must attach the event handlers on the '#todos' div by
+ * creating [jQuery.Controller.prototype.setup|a new controller instance]. There are 2 ways of doing this.
+ *
+ * @codestart
+ * //1. Create a new controller directly:
+ * new Todos($('#todos'));
+ * //2. Use jQuery function
+ * $('#todos').todos();
+ * @codeend
+ *
+ * ## Controller Initialization
+ *
+ * It can be extremely useful to add an init method with
+ * setup functionality for your widget.
+ *
+ * In the following example, I create a controller that when created, will put a message as the content of the element:
+ *
+ * $.Controller("SpecialController",
+ * {
+ * init: function( el, message ) {
+ * this.element.html(message)
+ * }
+ * })
+ * $(".special").special("Hello World")
+ *
+ * ## Removing Controllers
+ *
+ * Controller removal is built into jQuery. So to remove a controller, you just have to remove its element:
+ *
+ * @codestart
+ * $(".special_controller").remove()
+ * $("#containsControllers").html("")
+ * @codeend
+ *
+ * It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed.
+ *
+ * If you just want to remove controller functionality, call destroy on the controller instance:
+ *
+ * @codestart
+ * $(".special_controller").controller().destroy()
+ * @codeend
+ *
+ * ## Accessing Controllers
+ *
+ * Often you need to get a reference to a controller, there are a few ways of doing that. For the
+ * following example, we assume there are 2 elements with className="special".
+ *
+ * @codestart
+ * //creates 2 foo controllers
+ * $(".special").foo()
+ *
+ * //creates 2 bar controllers
+ * $(".special").bar()
+ *
+ * //gets all controllers on all elements:
+ * $(".special").controllers() //-> [foo, bar, foo, bar]
+ *
+ * //gets only foo controllers
+ * $(".special").controllers(FooController) //-> [foo, foo]
+ *
+ * //gets all bar controllers
+ * $(".special").controllers(BarController) //-> [bar, bar]
+ *
+ * //gets first controller
+ * $(".special").controller() //-> foo
+ *
+ * //gets foo controller via data
+ * $(".special").data("controllers")["FooController"] //-> foo
+ * @codeend
+ *
+ * ## Calling methods on Controllers
+ *
+ * Once you have a reference to an element, you can call methods on it. However, Controller has
+ * a few shortcuts:
+ *
+ * @codestart
+ * //creates foo controller
+ * $(".special").foo({name: "value"})
+ *
+ * //calls FooController.prototype.update
+ * $(".special").foo({name: "value2"})
+ *
+ * //calls FooController.prototype.bar
+ * $(".special").foo("bar","something I want to pass")
+ * @codeend
+ *
+ * These methods let you call one controller from another controller.
+ *
+ */
+ $.Class("jQuery.Controller",
+ /**
+ * @Static
+ */
+ {
+ /**
+ * Does 2 things:
+ *
+ * - Creates a jQuery helper for this controller.pluginName property allows you
+ * to change the jQuery plugin helper name from its
+ * default value.
+ *
+ * $.Controller("Mxui.Layout.Fill",{
+ * pluginName: "fillWith"
+ * },{});
+ *
+ * $("#foo").fillWith();
+ */
+ pluginName = this.pluginName || this._fullName,
+ funcName;
+
+ // create jQuery plugin
+ if(pluginName !== 'j_query_controller'){
+ this.plugin(pluginName);
+ }
+
+
+ // make sure listensTo is an array
+
+ // calculate and cache actions
+ this.actions = {};
+
+ for ( funcName in this.prototype ) {
+ if (funcName == 'constructor' || !isFunction(this.prototype[funcName]) ) {
+ continue;
+ }
+ if ( this._isAction(funcName) ) {
+ this.actions[funcName] = this._action(funcName);
+ }
+ }
+ },
+ /**
+ * @hide
+ * @param {String} methodName a prototype function
+ * @return {Boolean} truthy if an action or not
+ */
+ _isAction: function( methodName ) {
+ if ( actionMatcher.test(methodName) ) {
+ return true;
+ } else {
+ return $.inArray(methodName, this.listensTo) > -1 || $.event.special[methodName] || processors[methodName];
+ }
+
+ },
+ plugin : function(){},
+ /**
+ * @hide
+ * This takes a method name and the options passed to a controller
+ * and tries to return the data necessary to pass to a processor
+ * (something that binds things).
+ *
+ * For performance reasons, this called twice. First, it is called when
+ * the Controller class is created. If the methodName is templated
+ * like : "{window} foo", it returns null. If it is not templated
+ * it returns event binding data.
+ *
+ * The resulting data is added to this.actions.
+ *
+ * When a controller instance is created, _action is called again, but only
+ * on templated actions.
+ *
+ * @param {Object} methodName the method that will be bound
+ * @param {Object} [options] first param merged with class default options
+ * @return {Object} null or the processor and pre-split parts.
+ * The processor is what does the binding/subscribing.
+ */
+ _action: function( methodName, options ) {
+ // reset the test index
+ parameterReplacer.lastIndex = 0;
+
+ //if we don't have options (a controller instance), we'll run this later
+ if (!options && parameterReplacer.test(methodName) ) {
+ return null;
+ }
+ // If we have options, run sub to replace templates "{}" with a value from the options
+ // or the window
+ var convertedName = options ? $.String.sub(methodName, [options, window]) : methodName,
+
+ // If a "{}" resolves to an object, convertedName will be an array
+ arr = $.isArray(convertedName),
+
+ // get the parts of the function = [convertedName, delegatePart, eventPart]
+ parts = (arr ? convertedName[1] : convertedName).match(breaker),
+ event = parts[2],
+ processor = processors[event] || basicProcessor;
+ return {
+ processor: processor,
+ parts: parts,
+ delegate : arr ? convertedName[0] : undefined
+ };
+ },
+ /**
+ * @attribute processors
+ * An object of {eventName : function} pairs that Controller uses to hook up events
+ * auto-magically. A processor function looks like:
+ *
+ * jQuery.Controller.processors.
+ * myprocessor = function( el, event, selector, cb, controller ) {
+ * //el - the controller's element
+ * //event - the event (myprocessor)
+ * //selector - the left of the selector
+ * //cb - the function to call
+ * //controller - the binding controller
+ * };
+ *
+ * This would bind anything like: "foo~3242 myprocessor".
+ *
+ * The processor must return a function that when called,
+ * unbinds the event handler.
+ *
+ * Controller already has processors for the following events:
+ *
+ * - change
+ * - click
+ * - contextmenu
+ * - dblclick
+ * - focusin
+ * - focusout
+ * - keydown
+ * - keyup
+ * - keypress
+ * - mousedown
+ * - mouseenter
+ * - mouseleave
+ * - mousemove
+ * - mouseout
+ * - mouseover
+ * - mouseup
+ * - reset
+ * - resize
+ * - scroll
+ * - select
+ * - submit
+ *
+ * Listen to events on the document or window
+ * with templated event handlers:
+ *
+ *
+ * $.Controller('Sized',{
+ * "{window} resize" : function(){
+ * this.element.width(this.element.parent().width() / 2);
+ * }
+ * });
+ *
+ * $('.foo').sized();
+ */
+ processors: {},
+ /**
+ * @attribute listensTo
+ * An array of special events this controller
+ * listens too. You only need to add event names that
+ * are whole words (ie have no special characters).
+ *
+ * $.Controller('TabPanel',{
+ * listensTo : ['show']
+ * },{
+ * 'show' : function(){
+ * this.element.show();
+ * }
+ * })
+ *
+ * $('.foo').tab_panel().trigger("show");
+ *
+ */
+ listensTo: [],
+ /**
+ * @attribute defaults
+ * A object of name-value pairs that act as default values for a controller's
+ * [jQuery.Controller.prototype.options options].
+ *
+ * $.Controller("Message",
+ * {
+ * defaults : {
+ * message : "Hello World"
+ * }
+ * },{
+ * init : function(){
+ * this.element.text(this.options.message);
+ * }
+ * })
+ *
+ * $("#el1").message(); //writes "Hello World"
+ * $("#el12").message({message: "hi"}); //writes hi
+ *
+ * In [jQuery.Controller.prototype.setup setup] the options passed to the controller
+ * are merged with defaults. This is not a deep merge.
+ */
+ defaults: {}
+ },
+ /**
+ * @Prototype
+ */
+ {
+ /**
+ * Setup is where most of controller's magic happens. It does the following:
+ *
+ * ### 1. Sets this.element
+ *
+ * The first parameter passed to new Controller(el, options) is expected to be
+ * an element. This gets converted to a jQuery wrapped element and set as
+ * [jQuery.Controller.prototype.element this.element].
+ *
+ * ### 2. Adds the controller's name to the element's className.
+ *
+ * Controller adds it's plugin name to the element's className for easier
+ * debugging. For example, if your Controller is named "Foo.Bar", it adds
+ * "foo_bar" to the className.
+ *
+ * ### 3. Saves the controller in $.data
+ *
+ * A reference to the controller instance is saved in $.data. You can find
+ * instances of "Foo.Bar" like:
+ *
+ * $("#el").data("controllers")['foo_bar'].
+ *
+ * ### Binds event handlers
+ *
+ * Setup does the event binding described in [jquery.controller.listening Listening To Events].
+ *
+ * @param {HTMLElement} element the element this instance operates on.
+ * @param {Object} [options] option values for the controller. These get added to
+ * this.options and merged with [jQuery.Controller.static.defaults defaults].
+ * @return {Array} return an array if you wan to change what init is called with. By
+ * default it is called with the element and options passed to the controller.
+ */
+ setup: function( element, options ) {
+ var funcName, ready, cls = this.constructor;
+
+ //want the raw element here
+ element = element.jquery ? element[0] : element;
+
+ //set element and className on element
+ var pluginname = cls.pluginName || cls._fullName;
+
+ this.element = $(element)
+
+ if(pluginname !== 'j_query_controller') {
+ //set element and className on element
+ this.element.addClass(pluginname);
+
+ //set in data
+ (data(element) || data(element, {}))[pluginname] = this;
+ }
+
+
+
+ /**
+ * @attribute options
+ *
+ * Options are used to configure an controller. They are
+ * the 2nd argument
+ * passed to a controller (or the first argument passed to the
+ * [jquery.controller.plugin controller's jQuery plugin]).
+ *
+ * For example:
+ *
+ * $.Controller('Hello')
+ *
+ * var h1 = new Hello($('#content1'), {message: 'World'} );
+ * equal( h1.options.message , "World" )
+ *
+ * var h2 = $('#content2').hello({message: 'There'})
+ * .controller();
+ * equal( h2.options.message , "There" )
+ *
+ * Options are merged with [jQuery.Controller.static.defaults defaults] in
+ * [jQuery.Controller.prototype.setup setup].
+ *
+ * For example:
+ *
+ * $.Controller("Tabs",
+ * {
+ * defaults : {
+ * activeClass: "ui-active-state"
+ * }
+ * },
+ * {
+ * init : function(){
+ * this.element.addClass(this.options.activeClass);
+ * }
+ * })
+ *
+ * $("#tabs1").tabs() // adds 'ui-active-state'
+ * $("#tabs2").tabs({activeClass : 'active'}) // adds 'active'
+ *
+ * Options are typically updated by calling
+ * [jQuery.Controller.prototype.update update];
+ *
+ */
+ this.options = extend( extend(true, {}, cls.defaults), options);
+
+
+
+
+ // bind all event handlers
+ this.bind();
+
+ /**
+ * @attribute element
+ * The controller instance's delegated element. This
+ * is set by [jQuery.Controller.prototype.setup setup]. It
+ * is a jQuery wrapped element.
+ *
+ * For example, if I add MyWidget to a '#myelement' element like:
+ *
+ * $.Controller("MyWidget",{
+ * init : function(){
+ * this.element.css("color","red")
+ * }
+ * })
+ *
+ * $("#myelement").my_widget()
+ *
+ * MyWidget will turn #myelement's font color red.
+ *
+ * ## Using a different element.
+ *
+ * Sometimes, you want a different element to be this.element. A
+ * very common example is making progressively enhanced form widgets.
+ *
+ * To change this.element, overwrite Controller's setup method like:
+ *
+ * $.Controller("Combobox",{
+ * setup : function(el, options){
+ * this.oldElement = $(el);
+ * var newEl = $('');
+ * this.oldElement.wrap(newEl);
+ * this._super(newEl, options);
+ * },
+ * init : function(){
+ * this.element //-> the div
+ * },
+ * ".option click" : function(){
+ * // event handler bound on the div
+ * },
+ * destroy : function(){
+ * var div = this.element; //save reference
+ * this._super();
+ * div.replaceWith(this.oldElement);
+ * }
+ * }
+ */
+ return this.element;
+ },
+ /**
+ * Bind attaches event handlers that will be
+ * removed when the controller is removed.
+ *
+ * This used to be a good way to listen to events outside the controller's
+ * [jQuery.Controller.prototype.element element]. However,
+ * using templated event listeners is now the prefered way of doing this.
+ *
+ * ### Example:
+ *
+ * init: function() {
+ * // calls somethingClicked(el,ev)
+ * this.bind('click','somethingClicked')
+ *
+ * // calls function when the window is clicked
+ * this.bind(window, 'click', function(ev){
+ * //do something
+ * })
+ * },
+ * somethingClicked: function( el, ev ) {
+ *
+ * }
+ *
+ * @param {HTMLElement|jQuery.fn|Object} [el=this.element]
+ * The element to be bound. If an eventName is provided,
+ * the controller's element is used instead.
+ *
+ * @param {String} eventName The event to listen for.
+ * @param {Function|String} func A callback function or the String name of a controller function. If a controller
+ * function name is given, the controller function is called back with the bound element and event as the first
+ * and second parameter. Otherwise the function is called back like a normal bind.
+ * @return {Integer} The id of the binding in this._bindings
+ */
+ bind: function( el, eventName, func ) {
+ if( el === undefined ) {
+ //adds bindings
+ this._bindings = [];
+ //go through the cached list of actions and use the processor to bind
+
+ var cls = this.constructor,
+ bindings = this._bindings,
+ actions = cls.actions,
+ element = this.element;
+
+ for ( funcName in actions ) {
+ if ( actions.hasOwnProperty(funcName) ) {
+ ready = actions[funcName] || cls._action(funcName, this.options);
+ bindings.push(
+ ready.processor(ready.delegate || element,
+ ready.parts[2],
+ ready.parts[1],
+ funcName,
+ this));
+ }
+ }
+
+
+ //setup to be destroyed ... don't bind b/c we don't want to remove it
+ var destroyCB = shifter(this,"destroy");
+ element.bind("destroyed", destroyCB);
+ bindings.push(function( el ) {
+ $(el).unbind("destroyed", destroyCB);
+ });
+ return bindings.length;
+ }
+ if ( typeof el == 'string' ) {
+ func = eventName;
+ eventName = el;
+ el = this.element;
+ }
+ return this._binder(el, eventName, func);
+ },
+ _binder: function( el, eventName, func, selector ) {
+ if ( typeof func == 'string' ) {
+ func = shifter(this,func);
+ }
+ this._bindings.push(binder(el, eventName, func, selector));
+ return this._bindings.length;
+ },
+ _unbind : function(){
+ var el = this.element[0];
+ each(this._bindings, function( key, value ) {
+ value(el);
+ });
+ //adds bindings
+ this._bindings = [];
+ },
+ /**
+ * Delegate will delegate on an elememt and will be undelegated when the controller is removed.
+ * This is a good way to delegate on elements not in a controller's element._super when overwriting
+ * controller's destroy event. The base destroy functionality unbinds
+ * all event handlers the controller has created.
+ *
+ * You could call destroy manually on an element with ChangeText
+ * added like:
+ *
+ * $("#changed").change_text("destroy");
+ *
+ */
+ destroy: function() {
+ var Class= this.constructor;
+
+ var self = this,
+ pluginName = Class.pluginName || Class._fullName,
+ controllers;
+
+ // unbind bindings
+ this._unbind();
+
+ if(pluginName !== 'j_query_controller'){
+ // remove the className
+ this.element.removeClass(fname);
+
+ // remove from data
+ delete this.element.data("controllers")[fname];
+ }
+
+ $(this).triggerHandler("destroyed"); //in case we want to know if the controller is removed
+
+ this.element = null;
+ },
+ /**
+ * Queries from the controller's element.
+ * @codestart
+ * ".destroy_all click" : function() {
+ * this.find(".todos").remove();
+ * }
+ * @codeend
+ * @param {String} selector selection string
+ * @return {jQuery.fn} returns the matched elements
+ */
+ find: function( selector ) {
+ return this.element.find(selector);
+ }
+ });
+
+ var processors = $.Controller.processors,
+
+ //------------- PROCESSSORS -----------------------------
+ //processors do the binding. They return a function that
+ //unbinds when called.
+ //the basic processor that binds events
+ basicProcessor = function( el, event, selector, methodName, controller ) {
+ return binder(el, event, shifter(controller, methodName), selector);
+ };
+
+
+
+
+ //set common events to be processed as a basicProcessor
+ each("change click contextmenu dblclick keydown keyup keypress mousedown mousemove mouseout mouseover mouseup reset resize scroll select submit focusin focusout mouseenter mouseleave".split(" "), function( i, v ) {
+ processors[v] = basicProcessor;
+ });
+
+
+
+
+})(jQuery);
+(function(){
+
+ var getId = function( inst ) {
+ return inst[inst.constructor.id]
+ },
+ trigger = function(obj, event, args){
+ $.event.trigger(event, args, obj, true)
+ },
+ ajax = function(ajaxOb, data, type, dataType, success, error ) {
+
+
+ // if we get a string, handle it
+ if ( typeof ajaxOb == "string" ) {
+ // if there's a space, it's probably the type
+ var parts = ajaxOb.split(" ")
+ ajaxOb = {
+ url : parts.pop(),
+ type : parts.pop()
+ };
+ }
+
+ // if we are a non-array object, copy to a new attrs
+ ajaxOb.data = typeof data == "object" && !$.isArray(data) ?
+ $.extend(ajaxOb.data || {}, data) : data;
+
+
+ // get the url with any templated values filled out
+ ajaxOb.url = $.String.sub(ajaxOb.url, ajaxOb.data, true);
+
+ return $.ajax($.extend({
+ type: type || "post",
+ dataType: dataType ||"json",
+ success : success,
+ error: error
+ },ajaxOb));
+ },
+ makeRequest = function( self, type, success, error, method ) {
+ var deferred ,
+ args = [self.serialize()],
+ // the Model
+ model = self.constructor,
+ jqXHR;
+
+ // destroy does not need data
+ if ( type == 'destroy' ) {
+ args.shift();
+ }
+ // update and destroy need the id
+ if ( type !== 'create' ) {
+ args.unshift(getId(self))
+ }
+
+ jqXHR = model[type].apply(model, args);
+
+ deferred = jqXHR.pipe(function(data){
+ self[method || type + "d"](data, jqXHR);
+ return self
+ })
+ promise = deferred.promise();
+ // hook up abort
+ if(jqXHR.abort){
+ promise.abort = function(){
+ jqXHR.abort();
+ }
+ }
+
+ return promise.then(success)
+ .fail(error);
+ }
+
+ // 338
+ ajaxMethods =
+ /**
+ * @Static
+ */
+ {
+ create: function( str , method) {
+ return function( attrs ) {
+ return ajax(str || this._shortName, attrs)
+ };
+ },
+ update: function( str ) {
+ return function( id, attrs ) {
+
+ // move id to newId if changing id
+ attrs = attrs || {};
+ var identity = this.id;
+ if ( attrs[identity] && attrs[identity] !== id ) {
+ attrs["new" + $.String.capitalize(id)] = attrs[identity];
+ delete attrs[identity];
+ }
+ attrs[identity] = id;
+
+ return ajax( str || this._url, attrs, "put")
+ }
+ },
+ destroy: function( str ) {
+ return function( id ) {
+ var attrs = {};
+ attrs[this.id] = id;
+ return ajax( str || this._url, attrs, "delete")
+ }
+ },
+
+ findAll: function( str ) {
+ return function( params, success, error ) {
+ return ajax( str || this._shortName, params, "get", "json " + this.fullName + ".models", success, error);
+ };
+ },
+ findOne: function( str ) {
+ return function( params, success, error ) {
+ return ajax(str || this._url, params, "get", "json " + this.fullName + ".model", success, error);
+ };
+ }
+ };
+ var modelNum = 0;
+ $.Observe("jQuery.Model",{
+ setup : function(){
+ $.Observe.apply(this, arguments);
+ if(this === jQuery.Model){
+ return;
+ }
+ var self = this;
+
+ $.each(ajaxMethods, function(name, method){
+ var prop = self[name];
+ if ( typeof prop !== 'function' ) {
+ self[name] = method(prop);
+ }
+ });
+ if(self.fullName == "jQuery.Model"){
+ self.fullName = "Model"+(++modelNum);
+ }
+ //add ajax converters
+ var converters = {},
+ convertName = "* " + self.fullName + ".model";
+
+ converters[convertName + "s"] = $.proxy(self.models,self);
+ converters[convertName] = $.proxy(self.model,self);
+
+ $.ajaxSetup({
+ converters: converters
+ });
+ this._url = this._shortName+"/{"+this.id+"}"
+ },
+ id: "id",
+ models: function( instancesRawData ) {
+ if (!instancesRawData ) {
+ return null;
+ }
+ // get the list type
+ var res = new( this.List || ML),
+ // did we get an array
+ arr = $.isArray(instancesRawData),
+
+ // did we get a model list?
+ ml = (instancesRawData instanceof ML),
+ // get the raw array of objects
+ raw = arr ?
+ // if an array, return the array
+ instancesRawData :
+ // otherwise if a model list
+ (ml ?
+ // get the raw objects from the list
+ instancesRawData.serialize() :
+ // get the object's data
+ instancesRawData.data),
+ // the number of items
+ length = raw.length,
+ i = 0;
+
+
+ for (; i < length; i++ ) {
+ res.push(this.model(raw[i]));
+ }
+ if (!arr ) { //push other stuff onto array
+ $.each(instancesRawData, function(prop, val){
+ if ( prop !== 'data' ) {
+ res[prop] = val;
+ }
+ })
+ }
+ return res;
+ },
+ model: function( attributes ) {
+ if (!attributes ) {
+ return null;
+ }
+ if ( attributes instanceof this ) {
+ attributes = attributes.serialize();
+ }
+ return new this( attributes );
+ }
+ },{
+ isNew: function() {
+ var id = getId(this);
+ // id || id === 0?
+ return (id === undefined || id === null || id === ''); //if null or undefined
+ },
+ save: function( success, error ) {
+ return makeRequest(this, this.isNew() ? 'create' : 'update', success, error);
+ },
+ destroy: function( success, error ) {
+ return makeRequest(this, 'destroy', success, error, 'destroyed');
+ }
+ });
+
+ $.each([
+ /**
+ * @function created
+ * @hide
+ * Called by save after a new instance is created. Publishes 'created'.
+ * @param {Object} attrs
+ */
+ "created",
+ /**
+ * @function updated
+ * @hide
+ * Called by save after an instance is updated. Publishes 'updated'.
+ * @param {Object} attrs
+ */
+ "updated",
+ /**
+ * @function destroyed
+ * @hide
+ * Called after an instance is destroyed.
+ * - Publishes "shortName.destroyed".
+ * - Triggers a "destroyed" event on this model.
+ * - Removes the model from the global list if its used.
+ *
+ */
+ "destroyed"], function( i, funcName ) {
+ $.Model.prototype[funcName] = function( attrs ) {
+ var stub,
+ constructor = this.constructor;
+
+ // update attributes if attributes have been passed
+ stub = attrs && typeof attrs == 'object' && this.attr(attrs.attr ? attrs.attr() : attrs);
+
+ // call event on the instance
+ trigger(this,funcName);
+
+
+
+ // call event on the instance's Class
+ trigger(constructor,funcName, this);
+ };
+ });
+
+
+ var ML = $.Observe.List('jQuery.Model.List')
+
+})(jQuery);
+(function(){
+ /**
+ *
+ * ":type route" //
+ *
+ * @param {Object} el
+ * @param {Object} event
+ * @param {Object} selector
+ * @param {Object} cb
+ */
+ jQuery.Controller.processors.route = function(el, event, selector, funcName, controller){
+ $.route(selector||"")
+ var check = function(){
+ if($.route.attr('route') === (selector||"")){
+ controller[funcName]($.route.attr())
+ }
+ }
+ $.route.bind('route',check);
+ return function(){
+ $.route.unbind('route',check)
+ }
+ }
+})(jQuery);
+(function( $ ) {
+
+ // a path like string into something that's ok for an element ID
+ var toId = function( src ) {
+ return src.replace(/^\/\//, "").replace(/[\/\.]/g, "_");
+ },
+ makeArray = $.makeArray,
+ // used for hookup ids
+ id = 1;
+ // this might be useful for testing if html
+ // htmlTest = /^[\s\n\r\xA0]*<(.|[\r\n])*>[\s\n\r\xA0]*$/
+ /**
+ * @class jQuery.View
+ * @parent jquerymx
+ * @plugin jquery/view
+ * @test jquery/view/qunit.html
+ * @download dist/jquery.view.js
+ *
+ * @description A JavaScript template framework.
+ *
+ * View provides a uniform interface for using templates with
+ * jQuery. When template engines [jQuery.View.register register]
+ * themselves, you are able to:
+ *
+ * - Use views with jQuery extensions [jQuery.fn.after after], [jQuery.fn.append append],
+ * [jQuery.fn.before before], [jQuery.fn.html html], [jQuery.fn.prepend prepend],
+ * [jQuery.fn.replaceWith replaceWith], [jQuery.fn.text text].
+ * - Template loading from html elements and external files.
+ * - Synchronous and asynchronous template loading.
+ * - [view.deferreds Deferred Rendering].
+ * - Template caching.
+ * - Bundling of processed templates in production builds.
+ * - Hookup jquery plugins directly in the template.
+ *
+ * The [mvc.view Get Started with jQueryMX] has a good walkthrough of $.View.
+ *
+ * ## Use
+ *
+ *
+ * When using views, you're almost always wanting to insert the results
+ * of a rendered template into the page. jQuery.View overwrites the
+ * jQuery modifiers so using a view is as easy as:
+ *
+ * $("#foo").html('mytemplate.ejs',{message: 'hello world'})
+ *
+ * This code:
+ *
+ * - Loads the template a 'mytemplate.ejs'. It might look like:
+ * <h2><%= message %></h2>
+ *
+ * - Renders it with {message: 'hello world'}, resulting in:
+ * <div id='foo'>"<h2>hello world</h2></div>
+ *
+ * - Inserts the result into the foo element. Foo might look like:
+ * <div id='foo'><h2>hello world</h2></div>
+ *
+ * ## jQuery Modifiers
+ *
+ * You can use a template with the following jQuery modifiers:
+ *
+ * | [jQuery.fn.after after] | $('#bar').after('temp.jaml',{}); |
| [jQuery.fn.append append] | $('#bar').append('temp.jaml',{}); |
| [jQuery.fn.before before] | $('#bar').before('temp.jaml',{}); |
| [jQuery.fn.html html] | $('#bar').html('temp.jaml',{}); |
| [jQuery.fn.prepend prepend] | $('#bar').prepend('temp.jaml',{}); |
| [jQuery.fn.replaceWith replaceWith] | $('#bar').replaceWidth('temp.jaml',{}); |
| [jQuery.fn.text text] | $('#bar').text('temp.jaml',{}); |
<script type='text/ejs' id='recipes'>
+ * <% for(var i=0; i < recipes.length; i++){ %>
+ * <li><%=recipes[i].name %></li>
+ * <%} %>
+ * </script>
+ *
+ * Render with this template like:
+ *
+ * @codestart
+ * $("#foo").html('recipes',recipeData)
+ * @codeend
+ *
+ * Notice we passed the id of the element we want to render.
+ *
+ * ## From File
+ *
+ * You can pass the path of a template file location like:
+ *
+ * $("#foo").html('templates/recipes.ejs',recipeData)
+ *
+ * However, you typically want to make the template work from whatever page they
+ * are called from. To do this, use // to look up templates from JMVC root:
+ *
+ * $("#foo").html('//app/views/recipes.ejs',recipeData)
+ *
+ * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking
+ * up a thread (and adding helpers) even easier:
+ *
+ * $("#foo").html( this.view('recipes', recipeData) )
+ *
+ * ## Packaging Templates
+ *
+ * If you're making heavy use of templates, you want to organize
+ * them in files so they can be reused between pages and applications.
+ *
+ * But, this organization would come at a high price
+ * if the browser has to
+ * retrieve each template individually. The additional
+ * HTTP requests would slow down your app.
+ *
+ * Fortunately, [steal.static.views steal.views] can build templates
+ * into your production files. You just have to point to the view file like:
+ *
+ * steal.views('path/to/the/view.ejs');
+ *
+ * ## Asynchronous
+ *
+ * By default, retrieving requests is done synchronously. This is
+ * fine because StealJS packages view templates with your JS download.
+ *
+ * However, some people might not be using StealJS or want to delay loading
+ * templates until necessary. If you have the need, you can
+ * provide a callback paramter like:
+ *
+ * $("#foo").html('recipes',recipeData, function(result){
+ * this.fadeIn()
+ * });
+ *
+ * The callback function will be called with the result of the
+ * rendered template and 'this' will be set to the original jQuery object.
+ *
+ * ## Deferreds (3.0.6)
+ *
+ * If you pass deferreds to $.View or any of the jQuery
+ * modifiers, the view will wait until all deferreds resolve before
+ * rendering the view. This makes it a one-liner to make a request and
+ * use the result to render a template.
+ *
+ * The following makes a request for todos in parallel with the
+ * todos.ejs template. Once todos and template have been loaded, it with
+ * render the view with the todos.
+ *
+ * $('#todos').html("todos.ejs",Todo.findAll());
+ *
+ * ## Just Render Templates
+ *
+ * Sometimes, you just want to get the result of a rendered
+ * template without inserting it, you can do this with $.View:
+ *
+ * var out = $.View('path/to/template.jaml',{});
+ *
+ * ## Preloading Templates
+ *
+ * You can preload templates asynchronously like:
+ *
+ * $.get('path/to/template.jaml',{},function(){},'view');
+ *
+ * ## Supported Template Engines
+ *
+ * JavaScriptMVC comes with the following template languages:
+ *
+ * - EmbeddedJS
+ * <h2><%= message %></h2>
+ *
+ * - JAML
+ * h2(data.message);
+ *
+ * - Micro
+ * <h2>{%= message %}</h2>
+ *
+ * - jQuery.Tmpl
+ * <h2>${message}</h2>
+
+ *
+ * The popular Mustache
+ * template engine is supported in a 2nd party plugin.
+ *
+ * ## Using other Template Engines
+ *
+ * It's easy to integrate your favorite template into $.View and Steal. Read
+ * how in [jQuery.View.register].
+ *
+ * @constructor
+ *
+ * Looks up a template, processes it, caches it, then renders the template
+ * with data and optional helpers.
+ *
+ * With [stealjs StealJS], views are typically bundled in the production build.
+ * This makes it ok to use views synchronously like:
+ *
+ * @codestart
+ * $.View("//myplugin/views/init.ejs",{message: "Hello World"})
+ * @codeend
+ *
+ * If you aren't using StealJS, it's best to use views asynchronously like:
+ *
+ * @codestart
+ * $.View("//myplugin/views/init.ejs",
+ * {message: "Hello World"}, function(result){
+ * // do something with result
+ * })
+ * @codeend
+ *
+ * @param {String} view The url or id of an element to use as the template's source.
+ * @param {Object} data The data to be passed to the view.
+ * @param {Object} [helpers] Optional helper functions the view might use. Not all
+ * templates support helpers.
+ * @param {Object} [callback] Optional callback function. If present, the template is
+ * retrieved asynchronously. This is a good idea if you aren't compressing the templates
+ * into your view.
+ * @return {String} The rendered result of the view or if deferreds
+ * are passed, a deferred that will resolve to
+ * the rendered result of the view.
+ */
+ var $view = $.View = function( view, data, helpers, callback ) {
+ // if helpers is a function, it is actually a callback
+ if ( typeof helpers === 'function' ) {
+ callback = helpers;
+ helpers = undefined;
+ }
+
+ // see if we got passed any deferreds
+ var deferreds = getDeferreds(data);
+
+
+ if ( deferreds.length ) { // does data contain any deferreds?
+ // the deferred that resolves into the rendered content ...
+ var deferred = $.Deferred();
+
+ // add the view request to the list of deferreds
+ deferreds.push(get(view, true))
+
+ // wait for the view and all deferreds to finish
+ $.when.apply($, deferreds).then(function( resolved ) {
+ // get all the resolved deferreds
+ var objs = makeArray(arguments),
+ // renderer is last [0] is the data
+ renderer = objs.pop()[0],
+ // the result of the template rendering with data
+ result;
+
+ // make data look like the resolved deferreds
+ if ( isDeferred(data) ) {
+ data = usefulPart(resolved);
+ }
+ else {
+ // go through each prop in data again,
+ // replace the defferreds with what they resolved to
+ for ( var prop in data ) {
+ if ( isDeferred(data[prop]) ) {
+ data[prop] = usefulPart(objs.shift());
+ }
+ }
+ }
+ // get the rendered result
+ result = renderer(data, helpers);
+
+ //resolve with the rendered view
+ deferred.resolve(result);
+ // if there's a callback, call it back with the result
+ callback && callback(result);
+ });
+ // return the deferred ....
+ return deferred.promise();
+ }
+ else {
+ // no deferreds, render this bad boy
+ var response,
+ // if there's a callback function
+ async = typeof callback === "function",
+ // get the 'view' type
+ deferred = get(view, async);
+
+ // if we are async,
+ if ( async ) {
+ // return the deferred
+ response = deferred;
+ // and callback callback with the rendered result
+ deferred.done(function( renderer ) {
+ callback(renderer(data, helpers))
+ })
+ } else {
+ // otherwise, the deferred is complete, so
+ // set response to the result of the rendering
+ deferred.done(function( renderer ) {
+ response = renderer(data, helpers);
+ });
+ }
+
+ return response;
+ }
+ },
+ // makes sure there's a template, if not, has steal provide a warning
+ checkText = function( text, url ) {
+ if (!text.match(/[^\s]/) ) {
+
+ throw "$.View ERROR: There is no template or an empty template at " + url;
+ }
+ },
+ // returns a 'view' renderer deferred
+ // url - the url to the view template
+ // async - if the ajax request should be synchronous
+ get = function( url, async ) {
+ return $.ajax({
+ url: url,
+ dataType: "view",
+ async: async
+ });
+ },
+ // returns true if something looks like a deferred
+ isDeferred = function( obj ) {
+ return obj && $.isFunction(obj.always) // check if obj is a $.Deferred
+ },
+ // gets an array of deferreds from an object
+ // this only goes one level deep
+ getDeferreds = function( data ) {
+ var deferreds = [];
+
+ // pull out deferreds
+ if ( isDeferred(data) ) {
+ return [data]
+ } else {
+ for ( var prop in data ) {
+ if ( isDeferred(data[prop]) ) {
+ deferreds.push(data[prop]);
+ }
+ }
+ }
+ return deferreds;
+ },
+ // gets the useful part of deferred
+ // this is for Models and $.ajax that resolve to array (with success and such)
+ // returns the useful, content part
+ usefulPart = function( resolved ) {
+ return $.isArray(resolved) && resolved.length === 3 && resolved[1] === 'success' ? resolved[0] : resolved
+ };
+
+
+
+ // you can request a view renderer (a function you pass data to and get html)
+ // Creates a 'view' transport. These resolve to a 'view' renderer
+ // a 'view' renderer takes data and returns a string result.
+ // For example:
+ //
+ // $.ajax({dataType : 'view', src: 'foo.ejs'}).then(function(renderer){
+ // renderer({message: 'hello world'})
+ // })
+ $.ajaxTransport("view", function( options, orig ) {
+ // the url (or possibly id) of the view content
+ var url = orig.url,
+ // check if a suffix exists (ex: "foo.ejs")
+ suffix = url.match(/\.[\w\d]+$/),
+ type,
+ // if we are reading a script element for the content of the template
+ // el will be set to that script element
+ el,
+ // a unique identifier for the view (used for caching)
+ // this is typically derived from the element id or
+ // the url for the template
+ id,
+ // the AJAX request used to retrieve the template content
+ jqXHR,
+ // used to generate the response
+ response = function( text ) {
+ // get the renderer function
+ var func = type.renderer(id, text);
+ // cache if if we are caching
+ if ( $view.cache ) {
+ $view.cached[id] = func;
+ }
+ // return the objects for the response's dataTypes
+ // (in this case view)
+ return {
+ view: func
+ };
+ };
+
+ // if we have an inline template, derive the suffix from the 'text/???' part
+ // this only supports '' tags
+ if ( el = document.getElementById(url) ) {
+ suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2];
+ }
+
+ // if there is no suffix, add one
+ if (!suffix ) {
+ suffix = $view.ext;
+ url = url + $view.ext;
+ }
+
+ // convert to a unique and valid id
+ id = toId(url);
+
+ // if a absolute path, use steal to get it
+ // you should only be using // if you are using steal
+ if ( url.match(/^\/\//) ) {
+ var sub = url.substr(2);
+ url = typeof steal === "undefined" ?
+ url = "/" + sub :
+ steal.root.mapJoin(sub);
+ }
+
+ //set the template engine type
+ type = $view.types[suffix];
+
+ // return the ajax transport contract: http://api.jquery.com/extending-ajax/
+ return {
+ send: function( headers, callback ) {
+ // if it is cached,
+ if ( $view.cached[id] ) {
+ // return the catched renderer
+ return callback(200, "success", {
+ view: $view.cached[id]
+ });
+
+ // otherwise if we are getting this from a script elment
+ } else if ( el ) {
+ // resolve immediately with the element's innerHTML
+ callback(200, "success", response(el.innerHTML));
+ } else {
+ // make an ajax request for text
+ jqXHR = $.ajax({
+ async: orig.async,
+ url: url,
+ dataType: "text",
+ error: function() {
+ checkText("", url);
+ callback(404);
+ },
+ success: function( text ) {
+ // make sure we got some text back
+ checkText(text, url);
+ // cache and send back text
+ callback(200, "success", response(text))
+ }
+ });
+ }
+ },
+ abort: function() {
+ jqXHR && jqXHR.abort();
+ }
+ }
+ })
+ $.extend($view, {
+ /**
+ * @attribute hookups
+ * @hide
+ * A list of pending 'hookups'
+ */
+ hookups: {},
+ /**
+ * @function hookup
+ * Registers a hookup function that can be called back after the html is
+ * put on the page. Typically this is handled by the template engine. Currently
+ * only EJS supports this functionality.
+ *
+ * var id = $.View.hookup(function(el){
+ * //do something with el
+ * }),
+ * html = "<h2><%= message %></h2>
- *
- * - Renders it with {message: 'hello world'}, resulting in:
- * <div id='foo'>"<h2>hello world</h2></div>
- *
- * - Inserts the result into the foo element. Foo might look like:
- * <div id='foo'><h2>hello world</h2></div>
- *
- * ## jQuery Modifiers
- *
- * You can use a template with the following jQuery modifiers:
- *
- * | [jQuery.fn.after after] | $('#bar').after('temp.jaml',{}); |
| [jQuery.fn.append append] | $('#bar').append('temp.jaml',{}); |
| [jQuery.fn.before before] | $('#bar').before('temp.jaml',{}); |
| [jQuery.fn.html html] | $('#bar').html('temp.jaml',{}); |
| [jQuery.fn.prepend prepend] | $('#bar').prepend('temp.jaml',{}); |
| [jQuery.fn.replaceWith replaceWith] | $('#bar').replaceWidth('temp.jaml',{}); |
| [jQuery.fn.text text] | $('#bar').text('temp.jaml',{}); |
<script type='text/ejs' id='recipes'>
- * <% for(var i=0; i < recipes.length; i++){ %>
- * <li><%=recipes[i].name %></li>
- * <%} %>
- * </script>
- *
- * Render with this template like:
- *
- * @codestart
- * $("#foo").html('recipes',recipeData)
- * @codeend
- *
- * Notice we passed the id of the element we want to render.
- *
- * ## From File
- *
- * You can pass the path of a template file location like:
- *
- * $("#foo").html('templates/recipes.ejs',recipeData)
- *
- * However, you typically want to make the template work from whatever page they
- * are called from. To do this, use // to look up templates from JMVC root:
- *
- * $("#foo").html('//app/views/recipes.ejs',recipeData)
- *
- * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking
- * up a thread (and adding helpers) even easier:
- *
- * $("#foo").html( this.view('recipes', recipeData) )
- *
- * ## Packaging Templates
- *
- * If you're making heavy use of templates, you want to organize
- * them in files so they can be reused between pages and applications.
- *
- * But, this organization would come at a high price
- * if the browser has to
- * retrieve each template individually. The additional
- * HTTP requests would slow down your app.
- *
- * Fortunately, [steal.static.views steal.views] can build templates
- * into your production files. You just have to point to the view file like:
- *
- * steal.views('path/to/the/view.ejs');
- *
- * ## Asynchronous
- *
- * By default, retrieving requests is done synchronously. This is
- * fine because StealJS packages view templates with your JS download.
- *
- * However, some people might not be using StealJS or want to delay loading
- * templates until necessary. If you have the need, you can
- * provide a callback paramter like:
- *
- * $("#foo").html('recipes',recipeData, function(result){
- * this.fadeIn()
- * });
- *
- * The callback function will be called with the result of the
- * rendered template and 'this' will be set to the original jQuery object.
- *
- * ## Deferreds (3.0.6)
- *
- * If you pass deferreds to $.View or any of the jQuery
- * modifiers, the view will wait until all deferreds resolve before
- * rendering the view. This makes it a one-liner to make a request and
- * use the result to render a template.
- *
- * The following makes a request for todos in parallel with the
- * todos.ejs template. Once todos and template have been loaded, it with
- * render the view with the todos.
- *
- * $('#todos').html("todos.ejs",Todo.findAll());
- *
- * ## Just Render Templates
- *
- * Sometimes, you just want to get the result of a rendered
- * template without inserting it, you can do this with $.View:
- *
- * var out = $.View('path/to/template.jaml',{});
- *
- * ## Preloading Templates
- *
- * You can preload templates asynchronously like:
- *
- * $.get('path/to/template.jaml',{},function(){},'view');
- *
- * ## Supported Template Engines
- *
- * JavaScriptMVC comes with the following template languages:
- *
- * - EmbeddedJS
- * <h2><%= message %></h2>
- *
- * - JAML
- * h2(data.message);
- *
- * - Micro
- * <h2>{%= message %}</h2>
- *
- * - jQuery.Tmpl
- * <h2>${message}</h2>
-
- *
- * The popular Mustache
- * template engine is supported in a 2nd party plugin.
- *
- * ## Using other Template Engines
- *
- * It's easy to integrate your favorite template into $.View and Steal. Read
- * how in [jQuery.View.register].
- *
- * @constructor
- *
- * Looks up a template, processes it, caches it, then renders the template
- * with data and optional helpers.
- *
- * With [stealjs StealJS], views are typically bundled in the production build.
- * This makes it ok to use views synchronously like:
- *
- * @codestart
- * $.View("//myplugin/views/init.ejs",{message: "Hello World"})
- * @codeend
- *
- * If you aren't using StealJS, it's best to use views asynchronously like:
- *
- * @codestart
- * $.View("//myplugin/views/init.ejs",
- * {message: "Hello World"}, function(result){
- * // do something with result
- * })
- * @codeend
- *
- * @param {String} view The url or id of an element to use as the template's source.
- * @param {Object} data The data to be passed to the view.
- * @param {Object} [helpers] Optional helper functions the view might use. Not all
- * templates support helpers.
- * @param {Object} [callback] Optional callback function. If present, the template is
- * retrieved asynchronously. This is a good idea if you aren't compressing the templates
- * into your view.
- * @return {String} The rendered result of the view or if deferreds
- * are passed, a deferred that will resolve to
- * the rendered result of the view.
- */
- var $view = $.View = function( view, data, helpers, callback ) {
- // if helpers is a function, it is actually a callback
- if ( typeof helpers === 'function' ) {
- callback = helpers;
- helpers = undefined;
- }
-
- // see if we got passed any deferreds
- var deferreds = getDeferreds(data);
-
-
- if ( deferreds.length ) { // does data contain any deferreds?
- // the deferred that resolves into the rendered content ...
- var deferred = $.Deferred();
-
- // add the view request to the list of deferreds
- deferreds.push(get(view, true))
-
- // wait for the view and all deferreds to finish
- $.when.apply($, deferreds).then(function( resolved ) {
- // get all the resolved deferreds
- var objs = makeArray(arguments),
- // renderer is last [0] is the data
- renderer = objs.pop()[0],
- // the result of the template rendering with data
- result;
-
- // make data look like the resolved deferreds
- if ( isDeferred(data) ) {
- data = usefulPart(resolved);
- }
- else {
- // go through each prop in data again,
- // replace the defferreds with what they resolved to
- for ( var prop in data ) {
- if ( isDeferred(data[prop]) ) {
- data[prop] = usefulPart(objs.shift());
- }
- }
- }
- // get the rendered result
- result = renderer(data, helpers);
-
- //resolve with the rendered view
- deferred.resolve(result);
- // if there's a callback, call it back with the result
- callback && callback(result);
- });
- // return the deferred ....
- return deferred.promise();
- }
- else {
- // no deferreds, render this bad boy
- var response,
- // if there's a callback function
- async = typeof callback === "function",
- // get the 'view' type
- deferred = get(view, async);
-
- // if we are async,
- if ( async ) {
- // return the deferred
- response = deferred;
- // and callback callback with the rendered result
- deferred.done(function( renderer ) {
- callback(renderer(data, helpers))
- })
- } else {
- // otherwise, the deferred is complete, so
- // set response to the result of the rendering
- deferred.done(function( renderer ) {
- response = renderer(data, helpers);
- });
- }
-
- return response;
- }
- },
- // makes sure there's a template, if not, has steal provide a warning
- checkText = function( text, url ) {
- if (!text.match(/[^\s]/) ) {
- steal.dev.log("There is no template or an empty template at " + url)
- throw "$.View ERROR: There is no template or an empty template at " + url;
- }
- },
- // returns a 'view' renderer deferred
- // url - the url to the view template
- // async - if the ajax request should be synchronous
- get = function( url, async ) {
- return $.ajax({
- url: url,
- dataType: "view",
- async: async
- });
- },
- // returns true if something looks like a deferred
- isDeferred = function( obj ) {
- return obj && $.isFunction(obj.always) // check if obj is a $.Deferred
- },
- // gets an array of deferreds from an object
- // this only goes one level deep
- getDeferreds = function( data ) {
- var deferreds = [];
-
- // pull out deferreds
- if ( isDeferred(data) ) {
- return [data]
- } else {
- for ( var prop in data ) {
- if ( isDeferred(data[prop]) ) {
- deferreds.push(data[prop]);
- }
- }
- }
- return deferreds;
- },
- // gets the useful part of deferred
- // this is for Models and $.ajax that resolve to array (with success and such)
- // returns the useful, content part
- usefulPart = function( resolved ) {
- return $.isArray(resolved) && resolved.length === 3 && resolved[1] === 'success' ? resolved[0] : resolved
- };
-
-
-
- // you can request a view renderer (a function you pass data to and get html)
- // Creates a 'view' transport. These resolve to a 'view' renderer
- // a 'view' renderer takes data and returns a string result.
- // For example:
- //
- // $.ajax({dataType : 'view', src: 'foo.ejs'}).then(function(renderer){
- // renderer({message: 'hello world'})
- // })
- $.ajaxTransport("view", function( options, orig ) {
- // the url (or possibly id) of the view content
- var url = orig.url,
- // check if a suffix exists (ex: "foo.ejs")
- suffix = url.match(/\.[\w\d]+$/),
- type,
- // if we are reading a script element for the content of the template
- // el will be set to that script element
- el,
- // a unique identifier for the view (used for caching)
- // this is typically derived from the element id or
- // the url for the template
- id,
- // the AJAX request used to retrieve the template content
- jqXHR,
- // used to generate the response
- response = function( text ) {
- // get the renderer function
- var func = type.renderer(id, text);
- // cache if if we are caching
- if ( $view.cache ) {
- $view.cached[id] = func;
- }
- // return the objects for the response's dataTypes
- // (in this case view)
- return {
- view: func
- };
- };
-
- // if we have an inline template, derive the suffix from the 'text/???' part
- // this only supports '' tags
- if ( el = document.getElementById(url) ) {
- suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2];
- }
-
- // if there is no suffix, add one
- if (!suffix ) {
- suffix = $view.ext;
- url = url + $view.ext;
- }
-
- // convert to a unique and valid id
- id = toId(url);
-
- // if a absolute path, use steal to get it
- // you should only be using // if you are using steal
- if ( url.match(/^\/\//) ) {
- var sub = url.substr(2);
- url = typeof steal === "undefined" ?
- url = "/" + sub :
- steal.root.mapJoin(sub);
- }
+// add steal related code
- //set the template engine type
- type = $view.types[suffix];
-
- // return the ajax transport contract: http://api.jquery.com/extending-ajax/
- return {
- send: function( headers, callback ) {
- // if it is cached,
- if ( $view.cached[id] ) {
- // return the catched renderer
- return callback(200, "success", {
- view: $view.cached[id]
- });
-
- // otherwise if we are getting this from a script elment
- } else if ( el ) {
- // resolve immediately with the element's innerHTML
- callback(200, "success", response(el.innerHTML));
- } else {
- // make an ajax request for text
- jqXHR = $.ajax({
- async: orig.async,
- url: url,
- dataType: "text",
- error: function() {
- checkText("", url);
- callback(404);
- },
- success: function( text ) {
- // make sure we got some text back
- checkText(text, url);
- // cache and send back text
- callback(200, "success", response(text))
- }
- });
- }
- },
- abort: function() {
- jqXHR && jqXHR.abort();
- }
- }
- })
- $.extend($view, {
- /**
- * @attribute hookups
- * @hide
- * A list of pending 'hookups'
- */
- hookups: {},
- /**
- * @function hookup
- * Registers a hookup function that can be called back after the html is
- * put on the page. Typically this is handled by the template engine. Currently
- * only EJS supports this functionality.
- *
- * var id = $.View.hookup(function(el){
- * //do something with el
- * }),
- * html = "<h2><%= message %></h2>
+ *
+ * - Renders it with {message: 'hello world'}, resulting in:
+ * <div id='foo'>"<h2>hello world</h2></div>
+ *
+ * - Inserts the result into the foo element. Foo might look like:
+ * <div id='foo'><h2>hello world</h2></div>
+ *
+ * ## jQuery Modifiers
+ *
+ * You can use a template with the following jQuery modifiers:
+ *
+ * | [jQuery.fn.after after] | $('#bar').after('temp.jaml',{}); |
| [jQuery.fn.append append] | $('#bar').append('temp.jaml',{}); |
| [jQuery.fn.before before] | $('#bar').before('temp.jaml',{}); |
| [jQuery.fn.html html] | $('#bar').html('temp.jaml',{}); |
| [jQuery.fn.prepend prepend] | $('#bar').prepend('temp.jaml',{}); |
| [jQuery.fn.replaceWith replaceWith] | $('#bar').replaceWidth('temp.jaml',{}); |
| [jQuery.fn.text text] | $('#bar').text('temp.jaml',{}); |
<script type='text/ejs' id='recipes'>
+ * <% for(var i=0; i < recipes.length; i++){ %>
+ * <li><%=recipes[i].name %></li>
+ * <%} %>
+ * </script>
+ *
+ * Render with this template like:
+ *
+ * @codestart
+ * $("#foo").html('recipes',recipeData)
+ * @codeend
+ *
+ * Notice we passed the id of the element we want to render.
+ *
+ * ## From File
+ *
+ * You can pass the path of a template file location like:
+ *
+ * $("#foo").html('templates/recipes.ejs',recipeData)
+ *
+ * However, you typically want to make the template work from whatever page they
+ * are called from. To do this, use // to look up templates from JMVC root:
+ *
+ * $("#foo").html('//app/views/recipes.ejs',recipeData)
+ *
+ * Finally, the [jQuery.Controller.prototype.view controller/view] plugin can make looking
+ * up a thread (and adding helpers) even easier:
+ *
+ * $("#foo").html( this.view('recipes', recipeData) )
+ *
+ * ## Packaging Templates
+ *
+ * If you're making heavy use of templates, you want to organize
+ * them in files so they can be reused between pages and applications.
+ *
+ * But, this organization would come at a high price
+ * if the browser has to
+ * retrieve each template individually. The additional
+ * HTTP requests would slow down your app.
+ *
+ * Fortunately, [steal.static.views steal.views] can build templates
+ * into your production files. You just have to point to the view file like:
+ *
+ * steal.views('path/to/the/view.ejs');
+ *
+ * ## Asynchronous
+ *
+ * By default, retrieving requests is done synchronously. This is
+ * fine because StealJS packages view templates with your JS download.
+ *
+ * However, some people might not be using StealJS or want to delay loading
+ * templates until necessary. If you have the need, you can
+ * provide a callback paramter like:
+ *
+ * $("#foo").html('recipes',recipeData, function(result){
+ * this.fadeIn()
+ * });
+ *
+ * The callback function will be called with the result of the
+ * rendered template and 'this' will be set to the original jQuery object.
+ *
+ * ## Deferreds (3.0.6)
+ *
+ * If you pass deferreds to $.View or any of the jQuery
+ * modifiers, the view will wait until all deferreds resolve before
+ * rendering the view. This makes it a one-liner to make a request and
+ * use the result to render a template.
+ *
+ * The following makes a request for todos in parallel with the
+ * todos.ejs template. Once todos and template have been loaded, it with
+ * render the view with the todos.
+ *
+ * $('#todos').html("todos.ejs",Todo.findAll());
+ *
+ * ## Just Render Templates
+ *
+ * Sometimes, you just want to get the result of a rendered
+ * template without inserting it, you can do this with $.View:
+ *
+ * var out = $.View('path/to/template.jaml',{});
+ *
+ * ## Preloading Templates
+ *
+ * You can preload templates asynchronously like:
+ *
+ * $.get('path/to/template.jaml',{},function(){},'view');
+ *
+ * ## Supported Template Engines
+ *
+ * JavaScriptMVC comes with the following template languages:
+ *
+ * - EmbeddedJS
+ * <h2><%= message %></h2>
+ *
+ * - JAML
+ * h2(data.message);
+ *
+ * - Micro
+ * <h2>{%= message %}</h2>
+ *
+ * - jQuery.Tmpl
+ * <h2>${message}</h2>
+
+ *
+ * The popular Mustache
+ * template engine is supported in a 2nd party plugin.
+ *
+ * ## Using other Template Engines
+ *
+ * It's easy to integrate your favorite template into $.View and Steal. Read
+ * how in [jQuery.View.register].
+ *
+ * @constructor
+ *
+ * Looks up a template, processes it, caches it, then renders the template
+ * with data and optional helpers.
+ *
+ * With [stealjs StealJS], views are typically bundled in the production build.
+ * This makes it ok to use views synchronously like:
+ *
+ * @codestart
+ * $.View("//myplugin/views/init.ejs",{message: "Hello World"})
+ * @codeend
+ *
+ * If you aren't using StealJS, it's best to use views asynchronously like:
+ *
+ * @codestart
+ * $.View("//myplugin/views/init.ejs",
+ * {message: "Hello World"}, function(result){
+ * // do something with result
+ * })
+ * @codeend
+ *
+ * @param {String} view The url or id of an element to use as the template's source.
+ * @param {Object} data The data to be passed to the view.
+ * @param {Object} [helpers] Optional helper functions the view might use. Not all
+ * templates support helpers.
+ * @param {Object} [callback] Optional callback function. If present, the template is
+ * retrieved asynchronously. This is a good idea if you aren't compressing the templates
+ * into your view.
+ * @return {String} The rendered result of the view or if deferreds
+ * are passed, a deferred that will resolve to
+ * the rendered result of the view.
+ */
+ var $view = $.View = function( view, data, helpers, callback ) {
+ // if helpers is a function, it is actually a callback
+ if ( typeof helpers === 'function' ) {
+ callback = helpers;
+ helpers = undefined;
+ }
+
+ // see if we got passed any deferreds
+ var deferreds = getDeferreds(data);
+
+
+ if ( deferreds.length ) { // does data contain any deferreds?
+ // the deferred that resolves into the rendered content ...
+ var deferred = $.Deferred();
+
+ // add the view request to the list of deferreds
+ deferreds.push(get(view, true))
+
+ // wait for the view and all deferreds to finish
+ $.when.apply($, deferreds).then(function( resolved ) {
+ // get all the resolved deferreds
+ var objs = makeArray(arguments),
+ // renderer is last [0] is the data
+ renderer = objs.pop()[0],
+ // the result of the template rendering with data
+ result;
+
+ // make data look like the resolved deferreds
+ if ( isDeferred(data) ) {
+ data = usefulPart(resolved);
+ }
+ else {
+ // go through each prop in data again,
+ // replace the defferreds with what they resolved to
+ for ( var prop in data ) {
+ if ( isDeferred(data[prop]) ) {
+ data[prop] = usefulPart(objs.shift());
+ }
+ }
+ }
+ // get the rendered result
+ result = renderer(data, helpers);
+
+ //resolve with the rendered view
+ deferred.resolve(result);
+ // if there's a callback, call it back with the result
+ callback && callback(result);
+ });
+ // return the deferred ....
+ return deferred.promise();
+ }
+ else {
+ // no deferreds, render this bad boy
+ var response,
+ // if there's a callback function
+ async = typeof callback === "function",
+ // get the 'view' type
+ deferred = get(view, async);
+
+ // if we are async,
+ if ( async ) {
+ // return the deferred
+ response = deferred;
+ // and callback callback with the rendered result
+ deferred.done(function( renderer ) {
+ callback(renderer(data, helpers))
+ })
+ } else {
+ // otherwise, the deferred is complete, so
+ // set response to the result of the rendering
+ deferred.done(function( renderer ) {
+ response = renderer(data, helpers);
+ });
+ }
+
+ return response;
+ }
+ },
+ // makes sure there's a template, if not, has steal provide a warning
+ checkText = function( text, url ) {
+ if (!text.match(/[^\s]/) ) {
+ steal.dev.log("There is no template or an empty template at " + url)
+ throw "$.View ERROR: There is no template or an empty template at " + url;
+ }
+ },
+ // returns a 'view' renderer deferred
+ // url - the url to the view template
+ // async - if the ajax request should be synchronous
+ get = function( url, async ) {
+ return $.ajax({
+ url: url,
+ dataType: "view",
+ async: async
+ });
+ },
+ // returns true if something looks like a deferred
+ isDeferred = function( obj ) {
+ return obj && $.isFunction(obj.always) // check if obj is a $.Deferred
+ },
+ // gets an array of deferreds from an object
+ // this only goes one level deep
+ getDeferreds = function( data ) {
+ var deferreds = [];
+
+ // pull out deferreds
+ if ( isDeferred(data) ) {
+ return [data]
+ } else {
+ for ( var prop in data ) {
+ if ( isDeferred(data[prop]) ) {
+ deferreds.push(data[prop]);
+ }
+ }
+ }
+ return deferreds;
+ },
+ // gets the useful part of deferred
+ // this is for Models and $.ajax that resolve to array (with success and such)
+ // returns the useful, content part
+ usefulPart = function( resolved ) {
+ return $.isArray(resolved) && resolved.length === 3 && resolved[1] === 'success' ? resolved[0] : resolved
+ };
+
+
+
+ // you can request a view renderer (a function you pass data to and get html)
+ // Creates a 'view' transport. These resolve to a 'view' renderer
+ // a 'view' renderer takes data and returns a string result.
+ // For example:
+ //
+ // $.ajax({dataType : 'view', src: 'foo.ejs'}).then(function(renderer){
+ // renderer({message: 'hello world'})
+ // })
+ $.ajaxTransport("view", function( options, orig ) {
+ // the url (or possibly id) of the view content
+ var url = orig.url,
+ // check if a suffix exists (ex: "foo.ejs")
+ suffix = url.match(/\.[\w\d]+$/),
+ type,
+ // if we are reading a script element for the content of the template
+ // el will be set to that script element
+ el,
+ // a unique identifier for the view (used for caching)
+ // this is typically derived from the element id or
+ // the url for the template
+ id,
+ // the AJAX request used to retrieve the template content
+ jqXHR,
+ // used to generate the response
+ response = function( text ) {
+ // get the renderer function
+ var func = type.renderer(id, text);
+ // cache if if we are caching
+ if ( $view.cache ) {
+ $view.cached[id] = func;
+ }
+ // return the objects for the response's dataTypes
+ // (in this case view)
+ return {
+ view: func
+ };
+ };
+
+ // if we have an inline template, derive the suffix from the 'text/???' part
+ // this only supports '' tags
+ if ( el = document.getElementById(url) ) {
+ suffix = "."+el.type.match(/\/(x\-)?(.+)/)[2];
+ }
+
+ // if there is no suffix, add one
+ if (!suffix ) {
+ suffix = $view.ext;
+ url = url + $view.ext;
+ }
+
+ // convert to a unique and valid id
+ id = toId(url);
+
+ // if a absolute path, use steal to get it
+ // you should only be using // if you are using steal
+ if ( url.match(/^\/\//) ) {
+ var sub = url.substr(2);
+ url = typeof steal === "undefined" ?
+ url = "/" + sub :
+ steal.root.mapJoin(sub);
+ }
+
+ //set the template engine type
+ type = $view.types[suffix];
+
+ // return the ajax transport contract: http://api.jquery.com/extending-ajax/
+ return {
+ send: function( headers, callback ) {
+ // if it is cached,
+ if ( $view.cached[id] ) {
+ // return the catched renderer
+ return callback(200, "success", {
+ view: $view.cached[id]
+ });
+
+ // otherwise if we are getting this from a script elment
+ } else if ( el ) {
+ // resolve immediately with the element's innerHTML
+ callback(200, "success", response(el.innerHTML));
+ } else {
+ // make an ajax request for text
+ jqXHR = $.ajax({
+ async: orig.async,
+ url: url,
+ dataType: "text",
+ error: function() {
+ checkText("", url);
+ callback(404);
+ },
+ success: function( text ) {
+ // make sure we got some text back
+ checkText(text, url);
+ // cache and send back text
+ callback(200, "success", response(text))
+ }
+ });
+ }
+ },
+ abort: function() {
+ jqXHR && jqXHR.abort();
+ }
+ }
+ })
+ $.extend($view, {
+ /**
+ * @attribute hookups
+ * @hide
+ * A list of pending 'hookups'
+ */
+ hookups: {},
+ /**
+ * @function hookup
+ * Registers a hookup function that can be called back after the html is
+ * put on the page. Typically this is handled by the template engine. Currently
+ * only EJS supports this functionality.
+ *
+ * var id = $.View.hookup(function(el){
+ * //do something with el
+ * }),
+ * html = "