The javascriptmvc model plug-in provides many tools to better organize model data, such as verification, association, and list. However, the more core functions are service encapsulation, type conversion, and events.
Attribute and Observability
The most important thing for the model layer is to obtain and set the attributes of the model, and listen for changes to the Model instance. The observer mode can be used to solve this problem. MVC uses views to monitor model changes.
Javascriptmvc can easily achieve this observability. A good example is paging. there are often several paging controls on a page. One Control provides the buttons on the previous and next pages, and the other control provides the details displayed on the current page (for example: "1-20 items are being displayed"). All controls must have the same data:
- Offset-index of the first data displayed
- Limit-display of information per page
- Count-Total number of information items
We can use $. Model to create the model data:
var paginate = new $.Model({ offset: 0, limit: 20, count: 200});
Now the paginate variable can be viewed. It can be passed to paging controls that perform read/write operations on him or monitor its attribute changes. You can use the traditional method or the model. ATTR (name) method to access paginate attributes:
assertEqual( paginate.offset, 0 );assertEqual( paginate.attr('limit') , 20 );
When we click the next page, we need to increase the offset value. We can use model. ATTR (name, value) to change the attribute value. The following code implements paging:
paginate.attr('offset',20);
When the status of paginate is modified by a control, other controls need to be notified. You can use model. BIND (ATTR, success (EV, newval) to bind the feature attribute changes, and then update the control:
paginate.bind('offset', function(ev, newVal){ $('#details').text( 'Showing items ' + (newVal+1 )+ '-' + this.count )})
You can also bind 'updated. attr' to listen to any attribute changes:
paginate.bind('updated.attr', function(ev, newVal){ $('#details').text( 'Showing items ' + (newVal+1 )+ '-' + this.count )})
The following code is the code of the next-previous plug-in that accepts the paginate data:
$.fn.nextPrev = function(paginate){ this.delegate('.next','click', function(){ var nextOffset = paginate.offset+paginate.limit; if( nextOffset < paginate.count){ paginate.attr('offset', nextOffset ); } }) this.delegate('.prev','click', function(){ var nextOffset = paginate.offset-paginate.limit; if( 0 < paginate.offset ){ paginate.attr('offset', Math.max(0, nextOffset) ); } }); var self = this; paginate.bind('updated.attr', function(){ var next = self.find('.next'), prev = self.find('.prev'); if( this.offset == 0 ){ prev.removeClass('enabled'); } else { prev.removeClass('disabled'); } if( this.offset > this.count - this.limit ){ next.removeClass('enabled'); } else { next.removeClass('disabled'); } })};
This plug-in also has some problems. First, if the control is removed from the page, it cannot be unbound from paginate. We will discuss it in the Controller Chapter. Second, there is a logic to determine the offset to prevent it from being a negative number or exceeding the maximum value. This logic should be placed in the model. We need to add other judgments to limit and offset. We need to create a pagination class.
Extended Model
The model of javascriptmvc inherits from $. class. We create a model class, which is implemented by inheriting $. Model (name, [static,] prototype:
$.Model('Paginate',{ staticProperty: 'foo'},{ prototypeProperty: 'bar'})
There are many ways to make the paginate model more useful. You can first add the setter method to limit the values of offset and count.
Setters
The setter method is the prototype method of the model. It is named setname and has three parameters: Val (passed to the model. ATTR (name, Val), success, and error callback functions. Generally, the method returns the value to be set to the model instance, or executes the error method when an exception occurs. When an asynchronous setter is executed, the success method is executed. The setcount and setoffset instance methods are added in the following example, and the attribute is set to a negative number.
$.Model('Paginate',{ setCount : function(newCount, success, error){ return newCount < 0 ? 0 : newCount; }, setOffset : function(newOffset, success, error){ return newOffset < 0 ? 0 : Math.min(newOffset, !isNaN(this.count - 1) ? this.count : Infinity ) }})
Currently, the nextprev plug-in uses the following method to set the offset value:
this.delegate('.next','click', function(){ paginate.attr('offset', paginate.offset+paginate.limit);})this.delegate('.prev','click', function(){ paginate.attr('offset', paginate.offset-paginate.limit );});
Default Value
We can set staticThe defaults attribute is
The default value is set for the paginate instance. When a new instance is created, if no other value is set, the default value is used for the instance.
$.Model('Paginate',{ defaults : { count: Infinity, offset: 0, limit: 100 }},{ setCount : function(newCount, success, error){ ... }, setOffset : function(newOffset, success, error){ ... }})var paginate = new Paginate({count: 500});assertEqual(paginate.limit, 100);assertEqual(paginate.count, 500);
Now paginate is very stylish, but it will become better to add a helper method.
Auxiliary Methods
A prototype method is used to obtain or set useful data on an instance. In the following example, paginate contains the next and Prev methods to navigate to different pages. He also provides cannext and canprev methods to determine whether pages can be turned over.
$.Model('Paginate',{ defaults : { count: Infinity, offset: 0, limit: 100 }},{ setCount : function( newCount ){ return Math.max(0, newCount ); }, setOffset : function( newOffset ){ return Math.max( 0 , Math.min(newOffset, this.count ) ) }, next : function(){ this.attr('offset', this.offset+this.limit); }, prev : function(){ this.attr('offset', this.offset - this.limit ) }, canNext : function(){ return this.offset > this.count - this.limit }, canPrev : function(){ return this.offset > 0 }})
Now our jquery plug-ins are becoming more and more stylish.
$.fn.nextPrev = function(paginate){ this.delegate('.next','click', function(){ paginate.attr('offset', paginate.offset+paginate.limit); }) this.delegate('.prev','click', function(){ paginate.attr('offset', paginate.offset-paginate.limit ); }); var self = this; paginate.bind('updated.attr', function(){ self.find('.prev')[paginate.canPrev() ? 'addClass' : 'removeClass']('enabled') self.find('.next')[paginate.canNext() ? 'addClass' : 'removeClass']('enabled'); })};
Service Encapsulation
We have seen how useful $. model is for modeling Client states, but for most programs, data is stored on the server rather than on the client. The client needs data on the CREATE, retrieve, update, and delete (crud) servers. Maintaining client and server data is tricky, and $. model can solve this problem well. $. Model is quite flexible. It can process any service type and data type. Here we only introduce declarative state transfer (rest) and JSON.
The rest service uses URLs and HTTP transmission protocols post, get, put, and delete to create, retrieve, update, and delete data respectively. For example, a job service that allows you to create, retrieve, update, and delete tasks looks like this:
Action |
Verb) |
URL |
Body |
Response |
Create a task |
Post |
/Tasks |
Name = do the dishes |
{ "id" : 2, "name" : "do the dishes", "acl" : "rw" , "createdAt": 1303173531164 // April 18 2011}
|
Get a task |
Get |
/Task/2 |
|
{ "id" : 2, "name" : "do the dishes", "acl" : "rw" , "createdAt": 1303173531164 // April 18 2011}
|
Get tasks |
Get |
/Tasks |
|
[{ "id" : 1, "name" : "take out trash", "acl" : "r", "createdAt": 1303000731164 // April 16 2011},{ "id" : 2, "name" : "do the dishes", "acl" : "rw" , "createdAt": 1303173531164 // April 18 2011}]
|
Update a task |
Put |
/Task/2 |
Name = take out recycling |
{ "id" : 2, "name" : "take out recycling", "acl" : "rw" , "createdAt": 1303173531164 // April 18 2011}
|
Delete a task |
Delete |
/Task/2 |
|
{}
|
The following code connects to the server and allows us to create, retrieve, update, and delete tasks on the server.
$.Model("Task",{ create : "POST /tasks.json", findOne : "GET /tasks/{id}.json", findAll : "GET /tasks.json", update : "PUT /tasks/{id}.json", destroy : "DELETE /tasks/{id}.json"},{ });
The following code shows how to use a task to perform a crud task.
Action |
Code |
Description |
Create a task |
new Task({ name: 'do the dishes'}) .save( success( task, data ), error( jqXHR) ) -> taskDeferred
|
Create a model instance on the server, first usenew Model(attributes) . Then executesave() . In this example, we do not use the task attribute to create a request to save data. We provide two parameters for save:
success -If the execution succeeds, the task instance and data are obtained from the server.
error -Execution in case of an exception.
Save returns a latency for task creation. |
Get a task |
Task.findOne(params, success( task ), error( jqXHR) ) -> taskDeferred
|
Obtain a single instance from the server and provide three parameters:
params -Data transmitted to the server, for example:{id: 2} .
success -If the execution succeeds, the task instance and data are obtained from the server.
error -Execution in case of an exception.
Findone returns a delay to solve the task. |
Get tasks |
Task.findAll(params, success( tasks ), error( jqXHR) ) -> tasksDeferred
|
The task array returned from the server uses three parameters:
params -Data to pass to the server. Typically, it's an empty object ({} ) Or filters:{limit: 20, offset: 100} .
success -If the execution succeeds, the task instance and data are obtained from the server.
error -Execution in case of an exception.
Findone returns the tasks array for resolving the latency. |
Update a task |
task.attr('name','take out recycling');task.save( success( task, data ), error( jqXHR) ) -> taskDeferred
|
When an update is executed on the server, first modifyattr And then executesave()。 Save uses the same parameters to return the same latency as the created task. |
Destroy a task |
task.destroy( success( task, data ), error( jqXHR) ) -> taskDeferred
|
Destroy a task on the slave server with two parameters:
success -If the execution succeeds, the task instance and data are obtained from the server.
error -Execution in case of an exception.
The destroy return delay is used to destroy a task.
|
The task model becomes the contract of our service.
Type conversion
In JavaScript programming, we often encounter time conversion difficulties. For example, 1303173531164 represents limit l 18th and 2011. We need to use new date (1303173531164) to solve the problem. Fortunately, $. model provides us with the type conversion function, which can easily solve this problem. We need to define a type attribute and the corresponding conversion function. The type attribute is placed on a static object.In attributes, the conversion function is placed in the static object convert, as follows:
$.Model('Task',{ attributes : { createdAt : 'date' }, convert : { date : function(date){ return typeof date == 'number' ? new Date(date) : date; } }},{});
We can use the following method:
Task.findAll({}, function(tasks){ $.each(tasks, function(){ console.log( "Year = "+this.createdAt.fullYear() ) })});
Crud event
When an instance is created, updated, or destroyed, the model publishes an event. You can use model. BIND (event, callback (EV, instance) to listen for created, updated, or destroyed events on the model or its instance. When we want to know whether a task is created and added to the page, or when it is updated, we can use the following code:
Task.bind('created', function(ev, task){ var el = $('<li>').html(todo.name); el.appendTo($('#todos')); task.bind('updated', function(){ el.html(this.name) }).bind('destroyed', function(){ el.remove() })})
Javascriptmvc tutorial directory