????JFIF??x?x????'
Server IP : 104.21.112.1 / Your IP : 216.73.216.145 Web Server : LiteSpeed System : Linux premium151.web-hosting.com 4.18.0-553.44.1.lve.el8.x86_64 #1 SMP Thu Mar 13 14:29:12 UTC 2025 x86_64 User : tempvsty ( 647) PHP Version : 8.0.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/tempvsty/peekmysite.com/wp-content/plugins/motopress-content-editor/jquery/model/ |
Upload File : |
/*global OpenAjax: true */ steal('jquery/class', 'jquery/lang/string', function() { // Common helper methods taken from jQuery (or other places) // Keep here so someday can be abstracted var $String = $.String, getObject = $String.getObject, underscore = $String.underscore, classize = $String.classize, isArray = $.isArray, makeArray = $.makeArray, extend = $.extend, each = $.each, trigger = function(obj, event, args){ $.event.trigger(event, args, obj, true) }, // used to make an ajax request where // ajaxOb - a bunch of options // data - the attributes or data that will be sent // success - callback function // error - error callback // fixture - the name of the fixture (typically a path or something on $.fixture // type - the HTTP request type (defaults to "post") // dataType - how the data should return (defaults to "json") ajax = function(ajaxOb, data, success, error, fixture, type, dataType ) { // if we get a string, handle it if ( typeof ajaxOb == "string" ) { // if there's a space, it's probably the type var sp = ajaxOb.indexOf(" ") if ( sp > -1 ) { ajaxOb = { url: ajaxOb.substr(sp + 1), type: ajaxOb.substr(0, sp) } } else { ajaxOb = {url : ajaxOb} } } // 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", fixture: fixture, success : success, error: error },ajaxOb)); }, // guesses at a fixture name where // extra - where to look for 'MODELNAME'+extra fixtures (ex: "Create" -> '-recipeCreate') // or - if the first fixture fails, default to this fixture = function( model, extra, or ) { // get the underscored shortName of this Model var u = underscore(model.shortName), // the first place to look for fixtures f = "-" + u + (extra || ""); // if the fixture exists in $.fixture return $.fixture && $.fixture[f] ? // return the name f : // or return or or || // or return a fixture derived from the path "//" + underscore(model.fullName).replace(/\.models\..*/, "").replace(/\./g, "/") + "/fixtures/" + u + (extra || "") + ".json"; }, // takes attrs, and adds it to the attrs (so it can be added to the url) // if attrs already has an id, it means it's trying to update the id // in this case, it sets the new ID as newId. addId = function( model, attrs, id ) { attrs = attrs || {}; var identity = model.id; if ( attrs[identity] && attrs[identity] !== id ) { attrs["new" + $String.capitalize(id)] = attrs[identity]; delete attrs[identity]; } attrs[identity] = id; return attrs; }, // returns the best list-like object (list is passed) getList = function( type ) { var listType = type || $.Model.List || Array; return new listType(); }, // a helper function for getting an id from an instance getId = function( inst ) { return inst[inst.constructor.id] }, // 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"]; }); }, // helper makes a request to a static ajax method // it also calls updated, created, or destroyed // and it returns a deferred that resolvesWith self and the data // returned from the ajax request makeRequest = function( self, type, success, error, method ) { // create the deferred makeRequest will return var deferred = $.Deferred(), // on a successful ajax request, call the // updated | created | destroyed method // then resolve the deferred resolve = function( data ) { self[method || type + "d"](data); deferred.resolveWith(self, [self, data, type]); }, // on reject reject the deferred reject = function( data ) { deferred.rejectWith(self, [data]) }, // the args to pass to the ajax method args = [self.serialize(), resolve, reject], // the Model model = self.constructor, jqXHR, promise = deferred.promise(); // destroy does not need data if ( type == 'destroy' ) { args.shift(); } // update and destroy need the id if ( type !== 'create' ) { args.unshift(getId(self)) } // hook up success and error deferred.then(success); deferred.fail(error); // call the model's function and hook up // abort jqXHR = model[type].apply(model, args); if(jqXHR && jqXHR.abort){ promise.abort = function(){ jqXHR.abort(); } } return promise; }, // a quick way to tell if it's an object and not some string isObject = function( obj ) { return typeof obj === 'object' && obj !== null && obj; }, $method = function( name ) { return function( eventType, handler ) { return $.fn[name].apply($([this]), arguments); } }, bind = $method('bind'), unbind = $method('unbind'), STR_CONSTRUCTOR = 'constructor'; /** * @class jQuery.Model * @parent jquerymx * @download http://jmvcsite.heroku.com/pluginify?plugins[]=jquery/model/model.js * @test jquery/model/qunit.html * @plugin jquery/model * @description Models and apps data layer. * * Models super-charge an application's * data layer, making it easy to: * * - Get and modify data from the server * - Listen to changes in data * - Setting and retrieving models on elements * - Deal with lists of data * - Do other good stuff * * Model inherits from [jQuery.Class $.Class] and make use * of REST services and [http://api.jquery.com/category/deferred-object/ deferreds] * so these concepts are worth exploring. Also, * the [mvc.model Get Started with jQueryMX] has a good walkthrough of $.Model. * * * ## Get and modify data from the server * * $.Model makes connecting to a JSON REST service * really easy. The following models <code>todos</code> 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 <code>[$.Model::save save]\(success, error\)</code> * 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 * <code>[$.Model.findAll findAll]\(params, callback(items)\)</code>: * * 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 * <code>[$.Model.findOne findOne]\(params, callback(item)\)</code>: * * 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 * <code>save</code> 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 <code>[$.Model.prototype.destroy destroy]\(success, error\)</code> * 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 * or its properties are changed. Use * <code>Model.[$.Model.bind bind]\(eventType, handler(event, model)\)</code> * to listen to all events of type on a model and * <code>model.[$.Model.prototype.bind bind]\(eventType, handler(event)\)</code> * to listen to events on a specific instance. * * __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. * * <code>[jQuery.fn.model $.fn.model]\(item\)</code> lets you set or read a model * instance from an element: * * Todo.findAll({}, function( todos ) { * * $.each(todos, function(todo) { * $('<li>').model(todo) * .text(todo.name) * .appendTo('#todos') * }); * }); * * When a todo is deleted, get its element with * <code>item.[$.Model.prototype.elements elements]\(context\)</code> * 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 <code><%= model %></code> * * ## Lists * * [$.Model.List $.Model.List] lets you handle multiple model instances * with ease. A List acts just like an <code>Array</code>, but you can add special properties * to it and listen to events on it. * * <code>$.Model.List</code> has become its own plugin, read about it * [$.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 [$.Model.attributes attributes], * you can have model auto-convert values from the server into more useful types: * * $.Model('Todo',{ * attributes : { * dueDate: 'date' * } * },{}) * * ### Associations * * The [$.Model.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 * [$.Model.model model]<code>( data )</code> 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 <code>timeRemaining</code> 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: * [$.Model.findAll findAll], [$.Model.findOne findOne], * [$.Model.prototype.save save], and [$.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 by [$.Model.prototype.save save] to create a model instance on the server. * * The easiest way to implement create is to 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(); * * You can also implement create by yourself. Create gets called with: * * - `attrs` - the [$.Model.serialize serialized] model attributes. * - `success(newAttrs)` - a success handler. * - `error` - an error handler. * * 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 `POST /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(newAttrs) 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 by [$.Model.prototype.save save] to * update a model instance on the server. * * 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. * * 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. * 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. It calls <code>[$.Model.models]\(raw\)</code>. 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 //$.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 * [$.Model.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 * <code>Task.models</code> and <code>Person.model</code> * to convert the raw data into an array of Tasks and a Person. * * Note that the full names of the models themselves are <code>App.Models.Task</code> * and <code>App.Models.Person</code>. The _.model_ and _.models_ parts are appended * for the benefit of [$.Model.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 [$.Model.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 [$.Model.findAll] request * into an array (or [$.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 [$.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 ? raw.length : null, 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 [$.Model.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 [$.Model.convert], in that the keys of this object * correspond to the types specified in [$.Model.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() } }, /** * @function bind */ bind: bind, /** * @function unbind */ 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 [$.Model.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 set<i>AttributeName</i> 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 [$.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]; }, /** * @function bind * 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, /** * @function unbind * Unbinds an event handler from this instance. * Read [$.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 [$.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 [$.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 [$.Model.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.call(Class, 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 [$.Model.create] or * [$.Model.update] depending if the instance * [$.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] called with (instance,data) if a successful save. * @param {Function} [error] error handler function called with (jqXHR) 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 * [$.Model.destroy] with the id of the instance. * * @codestart * recipe.destroy(success, error); * @codeend * * If OpenAjax.hub is available, after a successful * destroy "<i>modelName</i>.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 [$.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 [$.Model.prototype.identity identity] function in their class name. Example: * * <div class='todo <%= todo.identity() %>'> ... </div> * * This also works if you hooked up the model: * * <div <%= todo %>> ... </div> * * Typically, you'll use this as a response to a Model Event: * * "{Todo} destroyed": function(Todo, event, todo){ * todo.elements(this.element).remove(); * } * * * @param {String|jQuery|element} context If provided, only elements inside this element * that represent this model will be returned. * * @return {jQuery} Returns a jQuery wrapped nodelist of elements that have this model instances * identity in their class name. */ elements: function( context ) { var id = this.identity(); if( this.constructor.escapeIdentity ) { id = id.replace(/([ #;&,.+*~\'%:"!^$[\]()=>|\/])/g,'\\$1') } return $("." + id, context); }, hookup: function( el ) { var shortName = this.constructor._shortName, models = $.data(el, "models") || $.data(el, "models", {}); $(el).addClass(shortName + " " + this.identity()); models[shortName] = this; } }); 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; // remove from the list if instance is destroyed if ( funcName === 'destroyed' && constructor.list ) { constructor.list.remove(getId(this)); } // update attributes if attributes have been passed stub = attrs && typeof attrs == 'object' && this.attrs(attrs.attrs ? attrs.attrs() : 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); return [this].concat(makeArray(arguments)); // return like this for this.proxy chains }; }); /** * @add jQuery.fn */ // break /** * @function models * Returns a list of models. If the models are of the same * type, and have a [$.Model.List], it will return * the models wrapped with the list. * * @codestart * $(".recipes").models() //-> [recipe, ...] * @codeend * * @param {jQuery.Class} [type] if present only returns models of the provided type. * @return {Array|$.Model.List} returns an array of model instances that are represented by the contained elements. */ $.fn.models = function( type ) { //get it from the data var collection = [], kind, ret, retType; this.each(function() { each($.data(this, "models") || {}, function( name, instance ) { //either null or the list type shared by all classes kind = kind === undefined ? instance.constructor.List || null : (instance.constructor.List === kind ? kind : null); collection.push(instance); }); }); ret = getList(kind); ret.push.apply(ret, unique(collection)); return ret; }; /** * @function model * * Returns the first model instance found from [jQuery.fn.models] or * sets the model instance on an element. * * //gets an instance * ".edit click" : function(el) { * el.closest('.todo').model().destroy() * }, * // sets an instance * list : function(items){ * var el = this.element; * $.each(item, function(item){ * $('<div/>').model(item) * .appendTo(el) * }) * } * * @param {Object} [type] The type of model to return. If a model instance is provided * it will add the model to the element. */ $.fn.model = function( type ) { if ( type && type instanceof $.Model ) { type.hookup(this[0]); return this; } else { return this.models.apply(this, arguments)[0]; } }; });