注册 登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

百鬼夜行

身是菩提树,心如明镜台,时时勤拂拭,勿使惹尘埃。

 
 
 

日志

 
 

Developing Backbone.js Applications(From Addy Osmani)  

2012-07-31 16:24:29|  分类: 插件及效果类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
http://addyosmani.github.com/backbone-fundamentals/#thebasics-models


  • Models
  • Collections
  • Routers
  • Views

Models

Backbone models contain interactive data for an application as well as the logic around this data. For example, we can use a model to represent the concept of a photo object including its attributes like tags, titles and a location.

Models can be created by extending Backbone.Model as follows:

var Photo = Backbone.Model.extend({     defaults: {         src: 'placeholder.jpg',         title: 'an image placeholder',         coordinates: [0,0]     },     initialize: function(){         this.on("change:src", function(){             var src = this.get("src");              console.log('Image source updated to ' + src);         });     },     changeSrc: function( source ){         this.set({ src: source });     } });  var somePhoto = new Photo({ src: "test.jpg", title:"testing"}); somePhoto.changeSrc("magic.jpg"); // which triggers "change:src" and logs an update message to the console.

Initialization

The initialize() method is called when a new instance of a model is created. Its use is optional, however you'll see why it's good practice to use it below.

var Photo = Backbone.Model.extend({     initialize: function(){         console.log('this model has been initialized');     } });  // We can then create our own instance of a photo as follows: var myPhoto = new Photo();

Getters & Setters

Model.get()

Model.get() provides easy access to a model's attributes. Attributes which are passed through to the model on instantiation are instantly available for retrieval.

var myPhoto = new Photo({ title: "My awesome photo",                            src:"boston.jpg",                            location: "Boston",                            tags:['the big game', 'vacation']}),      title = myPhoto.get("title"), //My awesome photo     location = myPhoto.get("location"), //Boston     tags = myPhoto.get("tags"), // ['the big game','vacation']     photoSrc = myPhoto.get("src"); //boston.jpg

Alternatively, if you wish to directly access all of the attributes in a model's instance directly, you can achieve this as follows:

var myAttributes = myPhoto.attributes; console.log(myAttributes);

It is best practice to use Model.set() or direct instantiation to set the values of a model's attributes.

Accessing Model.attributes directly is generally discouraged. Instead, should you need to read or clone data,Model.toJSON() is recommended for this purpose. If you would like to access or copy a model's attributes for purposes such as JSON stringification (e.g. for serialization prior to being passed to a view), this can be achieved using Model.toJSON(). Remember that this will return an object and JSON.stringify() should be used to get a string representation of the data:

var myAttributes = myPhoto.toJSON(); console.log(JSON.stringify(myattributes)); /* this returns:   { title: "My awesome photo",     src:"boston.jpg",     location: "Boston",     tags:['the big game', 'vacation']} */

Model.set()

Model.set() allows us to pass attributes into an instance of our model. Attributes can either be set during initialization or at any time afterwards. It's important to avoid trying to set a Model's attributes directly (for example, Model.caption = 'A new caption'). Backbone uses Model.set() to know when to broadcast that a model's data has changed.

var Photo = Backbone.Model.extend({     initialize: function(){         console.log('this model has been initialized');     } });  // Setting the value of attributes via instantiation var myPhoto = new Photo({ title: 'My awesome photo', location: 'Boston' });  var myPhoto2 = new Photo();  // Setting the value of attributes through Model.set() myPhoto2.set({ title:'Vacation in Florida', location: 'Florida' });

Default values

There are times when you want your model to have a set of default values (e.g. in a scenario where a complete set of data isn't provided by the user). This can be set using a property called defaults in your model.

var Photo = Backbone.Model.extend({     defaults: {         title: 'Another photo!',         tags:  ['untagged'],         location: 'home',         src: 'placeholder.jpg'     },     initialize: function(){     } });  var myPhoto = new Photo({ location: "Boston",                            tags:['the big game', 'vacation']}),     title   = myPhoto.get("title"), //Another photo!     location = myPhoto.get("location"), //Boston     tags = myPhoto.get("tags"), // ['the big game','vacation']     photoSrc = myPhoto.get("src"); //placeholder.jpg

Listening for changes to your model

Any and all of the attributes in a Backbone model can have listeners bound to them which detect when their values change. Listeners can be added to the initialize() function:

this.on('change', function(){     console.log('values for this model have changed'); });

In the following example, we log a message whenever a specific attribute (the title of our Photo model) is altered.

var Photo = Backbone.Model.extend({     defaults: {         title: 'Another photo!',         tags:  ['untagged'],         location: 'home',         src: 'placeholder.jpg'     },     initialize: function(){         console.log('this model has been initialized');         this.on("change:title", function(){             var title = this.get("title");             console.log("My title has been changed to.. " + title);         });     },      setTitle: function(newTitle){         this.set({ title: newTitle });     } });  var myPhoto = new Photo({ title:"Fishing at the lake", src:"fishing.jpg"}); myPhoto.setTitle('Fishing at sea');  //logs 'My title has been changed to.. Fishing at sea'

Validation

Backbone supports model validation through Model.validate(), which allows checking the attribute values for a model prior to them being set.

Validation functions can be as simple or complex as necessary. If the attributes provided are valid, nothing should be returned from .validate(). If they are invalid, a custom error can be returned instead.

A basic example for validation can be seen below:

var Photo = Backbone.Model.extend({     validate: function(attribs){         if(attribs.src === undefined){             return "Remember to set a source for your image!";         }     },      initialize: function(){         console.log('this model has been initialized');         this.on("error", function(model, error){             console.log(error);         });     } });  var myPhoto = new Photo(); myPhoto.set({ title: "On the beach" }); //logs Remember to set a source for your image!

Note: Backbone passes the attributes object by shallow copy to the validate function using the Underscore_.extend method. This means that it is not possible to change any Number, String or Boolean attribute by reference in the way that one might expect a JavaScript object to behave. As shallow copy doesn't copy objects by implicitly copying them, but rather, by reference, one ca change the attributes on those objects.

An example of this (by @fivetanley) is available here.


Views

Views in Backbone don't contain the markup for your application, but rather they are there to support models by defining the logic for how they should be represented to the user. This is usually achieved using JavaScript templating (e.g. Mustache, jQuery-tmpl, etc.). A view's render() function can be bound to a model's change() event, allowing the view to always be up to date without requiring a full page refresh.

Creating new views

Similar to the previous sections, creating a new view is relatively straight-forward. To create a new View, simply extendBackbone.View. I'll explain this code in detail below:

var PhotoSearch = Backbone.View.extend({     el: $('#results'),     render: function( event ){         var compiled_template = _.template( $("#results-template").html() );         this.$el.html( compiled_template(this.model.toJSON()) );         return this; //recommended as this enables calls to be chained.     },     events: {         "submit #searchForm":  "search",         "click .reset": "reset",         "click .advanced": "switchContext"     },     search: function( event ){         //executed when a form '#searchForm' has been submitted     },     reset: function( event ){         //executed when an element with class "reset" has been clicked.     },     switchContext: function( event ){         //executed when an element with class "advanced" has been clicked.     } });

What is el?

el is basically a reference to a DOM element and all views must have one. It allows for all of the contents of a view to be inserted into the DOM at once, which makes for faster rendering because the browser performs the minimum required reflows and repaints.

There are two ways to attach a DOM element to a view: the element already exists in the page or a new element is created for the view and added manually by the developer. If the element already exists in the page, you can set el as either a CSS selector that matches the element or a simple reference to the DOM element.

el: '#footer',  // OR el: document.getElementById( 'footer' )

If you want to create a new element for your view, set any combination of the following view's properties: tagName, idand className. A new element will be created for you by the framework and a reference to it will be available at theel property.

tagName: 'p', // required, but defaults to 'div' if not set className: 'container', // optional, you can assign multiple classes to this property like so 'container homepage' id: 'header', // optional

The above code creates the DOMElement below but doesn't append it to the DOM.

<p id="header" class="container"></p>

Understanding render()

render() is an optional function that defines the logic for rendering a template. We'll use Underscore's micro-templating in these examples, but remember you can use other templating frameworks if you prefer.

The _.template method in Underscore compiles JavaScript templates into functions which can be evaluated for rendering. In the above view, I'm passing the markup from a template with id results-template to _.template() to be compiled. Next, I set the html of the el DOM element to the output of processing a JSON version of the model associated with the view through the compiled template.

Presto! This populates the template, giving you a data-complete set of markup in just a few short lines of code.

The events attribute

The Backbone events attribute allows us to attach event listeners to either custom selectors, or directly to el if no selector is provided. An event takes the form {"eventName selector": "callbackFunction"} and a number of event-types are supported, including click, submit, mouseover, dblclick and more.

What isn't instantly obvious is that under the bonnet, Backbone uses jQuery's .delegate() to provide instant support for event delegation but goes a little further, extending it so that this always refers to the current view object. The only thing to really keep in mind is that any string callback supplied to the events attribute must have a corresponding function with the same name within the scope of your view.


Collections

Collections are sets of Models and are created by extending Backbone.Collection.

Normally, when creating a collection you'll also want to pass through a property specifying the model that your collection will contain, as well as any instance properties required.

In the following example, we create a PhotoCollection that will contain our Photo models:

var PhotoCollection = Backbone.Collection.extend({     model: Photo });

Getters and Setters

There are a few different ways to retrieve a model from a collection. The most straight-forward is to useCollection.get() which accepts a single id as follows:

var skiingEpicness = PhotoCollection.get(2);

Sometimes you may also want to get a model based on its client id. The client id is a property that Backbone automatically assigns models that have not yet been saved. You can get a model's client id from its .cid property.

var mySkiingCrash = PhotoCollection.getByCid(456);

Backbone Collections don't have setters as such, but do support adding new models via .add() and removing models via .remove().

var a = new Backbone.Model({ title: 'my vacation'}),     b = new Backbone.Model({ title: 'my holiday'});  var photoCollection = new PhotoCollection([a,b]); photoCollection.remove([a,b]);

Listening for events

As collections represent a group of items, we're also able to listen for add and remove events for when new models are added or removed from the collection. Here's an example:

var PhotoCollection = new Backbone.Collection(); PhotoCollection.on("add", function(photo) {   console.log("I liked " + photo.get("title") + ' its this one, right? '  + photo.get("src")); });  PhotoCollection.add([   {title: "My trip to Bali", src: "bali-trip.jpg"},   {title: "The flight home", src: "long-flight-oofta.jpg"},   {title: "Uploading pix", src: "too-many-pics.jpg"} ]);

In addition, we're able to bind a change event to listen for changes to models in the collection.

PhotoCollection.on("change:title", function(){     console.log("there have been updates made to this collection's titles");     });

Fetching models from the server

Collections.fetch() retrieves a default set of models from the server in the form of a JSON array. When this data returns, the current collection's contents will be replaced with the contents of the array.

var PhotoCollection = new Backbone.Collection; PhotoCollection.url = '/photos'; PhotoCollection.fetch();

Under the covers, Backbone.sync is the function called every time Backbone tries to read or save models to the server. It uses jQuery or Zepto's ajax implementations to make these RESTful requests, however this can be overridden as per your needs.

In the above example if we wanted to log an event when .sync() was called, we could do this:

Backbone.sync = function(method, model) {   console.log("I've been passed " + method + " with " + JSON.stringify(model)); };

Resetting/Refreshing Collections

Rather than adding or removing models individually, you might occasionally wish to update an entire collection at once.Collection.reset() allows us to replace an entire collection with new models as follows:

javascript PhotoCollection.reset([ {title: "My trip to Scotland", src: "scotland-trip.jpg"}, {title: "The flight from Scotland", src: "long-flight.jpg"}, {title: "Latest snap of lock-ness", src: "lockness.jpg"}]); Note that using Collection.reset() doesn't fire any add or remove events. A reset event is fired instead.

Underscore utility functions

As Backbone requires Underscore as a hard dependency, we're able to use many of the utilities it has to offer to aid with our application development. Here's an example of how Underscore's sortBy() method can be used to sort a collection of photos based on a particular attribute.

var sortedByAlphabet = PhotoCollection.sortBy(function (photo) {     return photo.get("title").toLowerCase(); });

The complete list of what Underscore can do is beyond the scope of this guide, but can be found in its official docs.

Routers

In Backbone, routers are used to help manage application state and for connecting URLs to application events. This is achieved using hash-tags with URL fragments, or using the browser's pushState and History API. Some examples of routes may be seen below:

http://unicorns.com/#whatsup http://unicorns.com/#search/seasonal-horns/page2

Note: An application will usually have at least one route mapping a URL route to a function that determines what happens when a user reaches that particular route. This relationship is defined as follows:

"route" : "mappedFunction"

Let us now define our first controller by extending Backbone.Router. For the purposes of this guide, we're going to continue pretending we're creating a photo gallery application that requires a GalleryRouter.

Note the inline comments in the code example below as they continue the rest of the lesson on routers.

var GalleryRouter = Backbone.Router.extend({     /* define the route and function maps for this router */     routes: {         "about" : "showAbout",         /*Sample usage: http://unicorns.com/#about*/          "photos/:id" : "getPhoto",         /*This is an example of using a ":param" variable which allows us to match          any of the components between two URL slashes*/         /*Sample usage: http://unicorns.com/#photos/5*/          "search/:query" : "searchPhotos",         /*We can also define multiple routes that are bound to the same map function,         in this case searchPhotos(). Note below how we're optionally passing in a          reference to a page number if one is supplied*/         /*Sample usage: http://unicorns.com/#search/lolcats*/          "search/:query/p:page" : "searchPhotos",         /*As we can see, URLs may contain as many ":param"s as we wish*/         /*Sample usage: http://unicorns.com/#search/lolcats/p1*/          "photos/:id/download/*imagePath" : "downloadPhoto",         /*This is an example of using a *splat. splats are able to match any number of          URL components and can be combined with ":param"s*/         /*Sample usage: http://unicorns.com/#photos/5/download/files/lolcat-car.jpg*/          /*If you wish to use splats for anything beyond default routing, it's probably a good          idea to leave them at the end of a URL otherwise you may need to apply regular         expression parsing on your fragment*/          "*other"    : "defaultRoute"         /*This is a default route that also uses a *splat. Consider the         default route a wildcard for URLs that are either not matched or where         the user has incorrectly typed in a route path manually*/         /*Sample usage: http://unicorns.com/#anything*/      },      showAbout: function(){     },      getPhoto: function(id){         /*          Note that the id matched in the above route will be passed to this function         */         console.log("You are trying to reach photo " + id);     },      searchPhotos: function(query, page){         var page_number = page || 1;         console.log("Page number: " + page_number + " of the results for " + query);     },      downloadPhoto: function(id, path){     },      defaultRoute: function(other){         console.log("Invalid. You attempted to reach:" + other);     } });  /* Now that we have a router setup, remember to instantiate it*/  var myGalleryRouter = new GalleryRouter();

As of Backbone 0.5+, it's possible to opt-in for HTML5 pushState support via window.history.pushState. This permits you to define routes such as http://www.scriptjunkie.com/just/an/example. This will be supported with automatic degradation when a user's browser doesn't support pushState. For the purposes of this tutorial, we'll use the hashtag method.

Backbone.history

Next, we need to initialize Backbone.history as it handles hashchange events in our application. This will automatically handle routes that have been defined and trigger callbacks when they've been accessed.

The Backbone.history.start() method will simply tell Backbone that it's OK to begin monitoring all hashchangeevents as follows:

Backbone.history.start(); Router.navigate();

As an aside, if you would like to save application state to the URL at a particular point you can use the .navigate()method to achieve this. It simply updates your URL fragment without the need to trigger the hashchange event:

/*Lets imagine we would like a specific fragment for when a user zooms into a photo*/ zoomPhoto: function(factor){     this.zoom(factor); //imagine this zooms into the image     this.navigate("zoom/" + factor); //updates the fragment for us, but doesn't trigger the route }

It is also possible for Router.navigate() to trigger the route as well as updating the URL fragment.

zoomPhoto: function(factor){     this.zoom(factor); //imagine this zooms into the image     this.navigate("zoom/" + factor, true); //updates the fragment for us and triggers the route }
  评论这张
 
阅读(691)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018