jQuery.Model.List class
plugin: jquery/model/list
download: jQuery.Model.List
test: qunit.html
Model.Lists manage a lists (or arrays) of model instances.Similar to $.Model,they are used to:
- create events when a list changes
- make Ajax requests on multiple instances
- add helper function for multiple instances (ACLs)
The todoapp demonstrates using a $.Controller to implement an interface for a$.Model.List.
Creating A List Class
Create a `$.Model.List class for a$.Model like:
$.Model(
'Todo')
$.Model.List(
'Todo.List',{
// static properties
},{
// prototype properties
})
This creates a Todo.List
class for the Todo
class. This creates some nifty magic that we will see soon.
static
properties aretypically used to describe how a list makes requests. prototype
properties are helper functions that operate on an instance of a list.
Make a Helper Function
Often, a user wants to select multiple items on a page andperform some action on them (for example, deleting them). The app needs toindicate if this is possible (for example, by enabling a "DELETE"button).
If we get todo data back like:
// GET /todos.json ->
[{
"id"
:
1,
"name"
:
"dishes",
"acl"
:
"rwd"
},{
"id"
:
2,
"name"
:
"laundry",
"acl"
:
"r"
}, ... ]
We can add a helper function to let us know if we candelete all the instances:
$.Model.List(
'Todo.List',{
},{
canDelete :
function(){
return
this
.grep(
function(todo){
return
todo.acl.indexOf(
"d") !=
0
}).length ==
this.length
}
})
canDelete
gets a listof all todos that have d in their acl. If all todos have d, then canDelete
returns true.
Get a List Instance
You can create a model list instance by using newTodo.List( instances )
like:
vartodos =
newTodo.List([
new
Todo({id:
1, name: ...}),
new
Todo({id:
2, name: ...}),
]);
And call canDelete
on it like:
todos.canDelete()
//-> boolean
BUT! $.Model, $.fn.models,and $.Model.List are designed to work with each other.
When you use Todo.findAll
, it will callbackwith an instance of Todo.List
:
Todo.findAll({},
function(todos){
todos.canDelete()
//-> boolean
})
If you are adding the model instance to elements andretrieving them back with $().models()
, it will return a instanceof Todo.List
. The following returns if the checked .todo
elements are deletable:
// get the checked inputs
$(
'.todo input:checked')
// get the todo elements
.closest(
'.todo')
// get the model list
.models()
// check canDelete
.canDelete()
Make Ajax Requests with Lists
After checking if we can delete the todos, we should deletethem from the server. Like $.Model
, we can add a static destroyurl:
$.Model.List(
'Todo.List',{
destroy :
'POST /todos/delete'
},{
canDelete :
function(){
return
this
.grep(
function(todo){
return
todo.acl.indexOf(
"d") !=
0
}).length ==
this.length
}
})
and call destroyon our list.
// get the checked inputs
vartodos = $(
'.todo input:checked')
// get the todo elements
.closest(
'.todo')
// get the model list
.models()
if( todos.canDelete() ) {
todos.destroy()
}
By default, destroy will create an AJAX request to deletethese instances on the server, when the AJAX request is successful, theinstances are removed from the list and events are dispatched.
Listening to events on Lists
Use bind(eventName,handler(event, data))
to listen to add, remove, and updated eventson a list.
When a model instance is destroyed, it is removed from alllists. In the todo example, we can bind to remove to know when a todo has beendestroyed. The following removes all the todo elements from the page when theyare removed from the list:
todos.bind(
'remove',
function(ev, removedTodos){
removedTodos.elements().remove();
})
Demo
The following demo illustrates the previous features with acontacts list. Check multiple Contacts and click "DESTROY ALL"
Demo
HTML
Source
Other List Features
- Store and retrieve multiple instances
- Fast HTML inserts
Store and retrieve multiple instances
Once you have a collection of models, you often want toretrieve and update that list with new instances. Storing and retrieving is apowerful feature you can leverage to manage and maintain a list of models.
To store a new model instance in a list...
listInstance.push(
newAnimal({ type: dog, id:
123}))
To later retrieve that instance in your list...
varanimal = listInstance.get(
123);
Faster Inserts
The 'easy' way to add a model to an element is simplyinserting the model into the view like:
<div <%= task %>> A task
</div>
And then you can use $('.task').models().
This pattern is fast enough for 90% of all widgets. But itdoes require an extra query. Lists help you avoid this.
The getmethod takes elements and uses their className to return matched instances inthe list.
To use get, your elements need to have the instance'sidentity in their className. So to setup a div to reprsent a task, you wouldhave the following in a view:
<div class='task <%= task.identity() %>'> A task
</div>
Then, with your model list, you could use get to get a listof tasks:
taskList.get($(
'.task'))
The following demonstrates how to use this technique:
Demo
HTML
<div id="contacts"></div>
<div id="update"></div>
Source
steal(
'jquery/model',
'jquery/dom/fixture'
,
'jquery/model/list'
,
function(){
// =============== SETUP FIXTURES ===============
$.fixture(
"GET /recipes.json",
function(){
return
[[{
'id':
1,
'name':
'Justin Meyer',
'birthday':
'1982-10-20'},
{
'id':
2,
'name':
'Brian Moschel',
'birthday':
'1983-11-10'},
{
'id':
3,
'name':
'Alex Gomes',
'birthday':
'1980-2-10'}]];
});
$.fixture(
"PUT /recipes/{id}.json",
function(){
return
{};
})
// =============== Contact Model ===============
$.Model(
"Contact",{
attributes : {
birthday :
'date'
},
convert : {
// a converter to handle dates like 12-12-2012
date :
function(raw){
if
(
typeofraw ==
'string'){
var
matches = raw.match(
/(\d+)-(\d+)-(\d+)/)
return
new
Date( +matches[
1],
(+matches[
2])-
1,
+matches[
3] )
}
elseif
(raw
instanceofDate){
return
raw;
}
}
},
findAll :
"/recipes.json",
update :
"/recipes/{id}.json"
},{
ageThisYear :
function(){
return
new
Date().getFullYear() -
this
.birthday.getFullYear()
},
getBirthday :
function(){
return
""
+
this.birthday.getFullYear()+
"-"
+(
this.birthday.getMonth()+
1)+
"-"
+
this.birthday.getDate();
}
});
// =============== updaterWidget ===============
makeAgeUpdater =
function(contact){
var
updater = $(
"#update")
// clear the old update content and add the contact's bday
updater.html(
"")
.append(contact.name+
"'s birthday");
// create an input element
$(
'<input/>')
// set the contacts birthday as val
.val(contact.attr(
"birthday"))
// when the input changes, update the contact with the value
.change(
function(){
contact.update({
'birthday'
:
this.value
})
})
// add the input to the updater
.appendTo(updater)
};
// =============== Listen for updates ===============
Contact.bind(
"updated",
function(ev, contact){
// use the list to get the instance from the element
contact.elements($(
'#contacts'))
.html(contact.name+
" "+contact.ageThisYear()+
" <a>Show</a>"
);
});
// =============== Draw out Contacts ===============
Contact.findAll({},
function(contacts){
var
contactsEl = $(
'#contacts'),
html = [],
contact;
// collect contact html
for
(
vari =
0; i < contacts.length;i++){
contact = contacts[i]
html.push(
"<li class='contact ",
contact.identity(),
//add the identity to the className manually
"'>"
,
contact.name+
" "+contact.ageThisYear()+
" <a>Show</a>"
,
"</li>"
)
}
// insert contacts html
contactsEl.html(html.join(
""))
contactsEl.delegate(
"li",
"click",
function(){
// use the contacts list to get the
// contact from the clicked element
var
contact = contacts.get(
this)[
0]
makeAgeUpdater( contact );
});
});
})
jQuery.Model.List.Cookie class
plugin: jquery/model/list/cookie
download: jQuery.Model.List.Cookie
test: qunit.html
Provides a store-able list of model instances. Thefollowing retrieves and saves a list of contacts:
varcontacts =
newContact.List([]).retrieve(
"contacts");
// add each contact to the page
contacts.each(
function(){
addContact(
this);
});
// when a new cookie is crated
$(
"#contact").submit(
function(ev){
ev.preventDefault();
var
data = $(
this).formParams();
// gives it a random id
data.id = +
newDate();
var
contact =
newContact(data);
//add it to the list of contacts
contacts.push(contact);
//store the current list
contacts.store(
"contacts");
//show the contact
addContact(contact);
})
You can see this in action in the following demo. Create acontact, then refresh the page.
Demo
HTML
<h2>Create A Contact
</h2>
<form action="" id="contact">
<label>
Name
</label>
<input name="name" type="text">
<br>
<label>
Birthday
</label>
<input name="birthday" value="1982-10-20" type="text">
(must be like 1982-10-20)
<br>
<input value="Create" type="submit">
</form>
<h2>List of Contacts
</h2>
<div id="contacts"></div>
Source
steal(
'jquery/model',
'jquery/model/list/cookie'
,
'jquery/dom/form_params'
).then(
function(){
$.Model(
"Contact",{
attributes : {
birthday :
'date'
},
convert : {
date :
function(raw){
if
(
typeofraw ==
'string'){
var
matches = raw.match(
/(\d+)-(\d+)-(\d+)/)
return
new
Date( +matches[
1],
(+matches[
2])-
1,
+matches[
3] )
}
elseif
(raw
instanceofDate){
return
raw;
}
}
}
},{
ageThisYear :
function(){
return
new
Date().getFullYear() -
this
.birthday.getFullYear()
},
getBirthday :
function(){
return
""
+
this.birthday.getFullYear()+
"-"
+(
this.birthday.getMonth()+
1)+
"-"
+
this.birthday.getDate();
}
});
// Create a contact list
$.Model.List.Cookie(
"Contact.List");
// A helper function for adding a contact to the page
varaddContact =
function(contact){
var
li = $(
'<li>')
.model(contact)
.html(contact.name+
" "+contact.ageThisYear())
.appendTo($(
"#contacts"));
}
$(
function(){
// pull saved contacts into this list
var
contacts =
newContact.List([]).retrieve(
"contacts");
// add each contact to the page
contacts.each(
function(){
addContact(
this);
});
// when a new cookie is crated
$(
"#contact").submit(
function(ev){
ev.preventDefault();
var
data = $(
this).formParams();
// gives it a random id
data.id = +
newDate();
var
contact =
newContact(data);
//add it to the list of contacts
contacts.push(contact);
//store the current list
contacts.store(
"contacts");
//show the contact
addContact(contact);
})
})
})
jQuery.Model.List.Cookie.prototype.retrieve function
Deserializes a list of instances in the cookie with theprovided name
API
model.list.cookie.retrieve(name) -> jQuery.Model
name {String}
the name of the cookie to use.
returns {jQuery.Model}
returns this model instance.
jQuery.Model.List.Cookie.prototype.store function
Serializes and saves this list of model instances to thecookie in name.
API
model.list.cookie.store(name) -> jQuery.Model
name {String}
the name of the cookie
returns {jQuery.Model}
returns this model instance.
jQuery.Model.List.Local class
plugin: jquery/model/list/local
download: jQuery.Model.List.Local
Works exactly the same as jQuery.Model.List.Cookieexcept uses a local store instead of cookies.
jQuery.Model.List.prototype.bind function
Listens for an events on this list. The only useful eventsare:
. add - when new items are added . update - when an item isupdated . remove - when items are removed from the list (typically because theyare destroyed).
Listen for items being added
list.bind(
'add',
function(ev, newItems){
})
Listen for items being removed
list.bind(
'remove',
function(ev, removedItems){
})
Listen for an item being updated
list.bind(
'update',
function(ev, updatedItem){
})
API
model.list.bind() -> undefined
returns {undefined}
jQuery.Model.List.prototype.destroy function
Destroys all items in this list. This will use the List's staticdestroy method.
list.destroy(
function(destroyedItems){
//success
},
function(){
//error
});
API
model.list.destroy(success, error) -> undefined
success {Function}
a handler called back with the destroyed items. Theoriginal list will be emptied.
error {Function}
a handler called back when the destroy was unsuccessful.
returns {undefined}
jQuery.Model.List.prototype.each function
Iterates through the list of model instances, calling thecallback function on each iteration.
list.each(
function(indexInList, modelOfList){
...
});
API
model.list.each(callback)
callback {Function}
The function that will be executed on every object.
jQuery.Model.List.prototype.elements function
Returns elements that represent this list. For this towork, your element's should us the identityfunction 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();
}
API
model.list.elements(context) -> jQuery
context {String|jQuery|element}
If provided, only elements inside this element thatrepresent this model will be returned.
returns {jQuery}
Returns a jQuery wrapped nodelist of elements that havethese model instances identities in their class names.
jQuery.Model.List.prototype.findAll function
Finds items and adds them to this list. This uses jQuery.Model.static.findAllto find items with the params passed.
API
model.list.findAll(params, success, error) -> undefined
params {Object}
options to refind the returned items
success {Function}
called with the list
error {Object}
returns {undefined}
jQuery.Model.List.prototype.get function
Gets a list of elements by ID or element.
To fetch by id:
varmatch = list.get(
23);
or to fetch by element:
varmatch = list.get($(
'#content')[
0])
API
model.list.get(args) -> undefined
args {Object}
elements or ids to retrieve.
jQuery.Model.List.prototype.grep function
Finds the instances of the list which satisfy a callbackfilter function. The original array is not affected.
varmatchedList = list.grep(
function(instanceInList, indexInArray){
return
instanceInList.date <
newDate();
});
API
model.list.grep(callback, args) -> undefined
callback {Function}
the function to call back. This function has the same callpattern as what jQuery.grep provides.
args {Object}
returns {undefined}
jQuery.Model.List.prototype.indexOf function
Finds the index of the item in the list. Returns -1 if notfound.
list.indexOf(item)
API
model.list.indexOf()
jQuery.Model.List.prototype.map function
Iterates through the list of model instances, calling thecallback function on each iteration.
list.map(
function(modelOfList, indexInList){
...
});
API
model.list.map(callback)
callback {Function}
The function to process each item against.
jQuery.Model.List.prototype.match function
Returns a list of all instances who's property matches thegiven value.
list.match(
'candy',
'snickers')
API
model.list.match(property, value) -> undefined
property {String}
the property to match
value {Object}
the value the property must equal
returns {undefined}
jQuery.Model.List.prototype.push function
Adds an instance or instances to the list
list.push(
newRecipe({id:
5, name:
"Water"}))
API
model.list.push(The) -> Number
The {args {Object}
instance(s) to push onto the list.
returns {Number}
The number of elements in the list after the new elementwas pushed in.
jQuery.Model.List.prototype.remove function
Removes instances from this list by id or by an element.
To remove by id:
varmatch = list.remove(
23);
or to remove by element:
varmatch = list.remove($(
'#content')[
0])
API
model.list.remove(args) -> undefined
args {Object}
elements or ids to remove.
jQuery.Model.List.prototype.reverse function
Reverse the list in place
list.reverse()
API
model.list.reverse()
jQuery.Model.List.prototype.shift function
Removes the first instance of the list, and returns thatinstance.
list.shift()
API
model.list.shift()
jQuery.Model.List.prototype.slice function
The slice method selects a part of an array, and returnsanother instance of this model list's class.
list.slice(start, end)
API
model.list.slice(start, end) -> undefined
start {Number}
the start index to select
end {Number}
the last index to select
returns {undefined}
jQuery.Model.List.prototype.sort function
Sorts the instances in the list.
list.sort(sortfunc)
API
model.list.sort()
jQuery.Model.List.prototype.splice function
The splice method adds and/or removes instances to/from thelist, and returns the removed instance(s).
list.splice(index,howmany)
API
model.list.splice()
jQuery.Model.List.prototype.unbind function
Unbinds an event on this list. Once all events are unbound,unbind stops listening to all elements in the collection.
list.unbind(
"update")
//unbinds all update events
API
model.list.unbind() -> undefined
returns {undefined}
jQuery.Model.List.prototype.unshift function
Adds a new instance to the beginning of an array, andreturns the new length.
list.unshift(element1,element2,...)
API
model.list.unshift()
jQuery.Model.List.prototype.update function
Updates items in the list with attributes. This makes arequest using the list class's staticupdate.
list.update(
function(updatedItems){
//success
},
function(){
//error
});
API
model.list.update(attrs, success, error) -> undefined
attrs {Object}
attributes to update the list with.
success {Function}
a handler called back with the updated items.
error {Function}
a handler called back when the update was unsuccessful.
returns {undefined}
jQuery.Model.List.static.destroy function
Destroy is used to remove a set of model instances from theserver. By implementing destroy along with the rest of the[jquery.model.services service api], your models provide an abstract serviceAPI.
You can implement destroy with a string like:
$.Model.List(
"Thing",{
destroy :
"POST /thing/destroy/"
})
Or you can implement destroy manually like:
$.Model.List(
"Thing",{
destroy :
function(ids, success, error){
return
$.ajax({
url:
"/thing/destroy/",
data: ids,
success: success,
error: error,
type:
"POST"
});
}
})
Then you delete models by calling the prototypedelete method.
listInstance.destroy();
By default, the request will POST an array of ids to bedeleted in the body of the Ajax request.
{
ids: [
5,
10,
20]
}
API
$.Model.List.destroy(ids, success, error)
ids {Array}
the ids of the instances you want destroyed
success {Function}
the callback function
error {Function}
a function to callback if something goes wrong.
jQuery.Model.List.static.update function
Update is used to update a set of model instances on theserver. By implementing update along with the rest of the[jquery.model.services service api], your models provide an abstract API forservices.
The easist way to implement update is to just give it theurl to put data to:
$.Model.List(
"Recipe",{
update:
"PUT /thing/update/"
},{})
Or you can implement update manually like:
$.Model.List(
"Thing",{
update :
function(ids, attrs, success, error){
return
$.ajax({
url:
"/thing/update/",
success: success,
type:
"PUT",
data: { ids: ids, attrs : attrs }
error: error
});
}
})
Then you update models by calling the prototypeupdate method.
listInstance.update({ name:
"Food"})
By default, the request will PUT an array of ids to beupdated and the changed attributes of the model instances in the body of theAjax request.
{
ids: [
5,
10,
20],
attrs: {
name:
"Food"
}
}
Your server should send back an object with any newattributes the model should have. For example if your server udpates the"updatedAt" property, it should send back something like:
// PUT /recipes/4,25,20 { name: "Food" } ->
{
updatedAt :
"10-20-2011"
}
API
$.Model.List.update(ids, attrs, success, error)
ids {Array}
the ids of the model instance
attrs {Object}
Attributes on the model instance
success {Function}
the callback function. It optionally accepts an object ofattribute / value pairs of property changes the client doesn't already knowabout. For example, when you update a name property, the server might updateother properties as well (such as updatedAt). The server should send theseproperties as the response to updates. Passing them to success will update themodel instances with these properties.
error {Function}
a function to callback if something goes wrong.