Vanjs backbone-powerpoint

Preview:

DESCRIPTION

 

Citation preview

Backbone in Multipage Apps

Michael Yagudaev@yagudaev

michael@0idle.com

July 2013

Outline About Me

Why Backbone?

Pain Points with Backbone

Design Patterns/Best Practices

Marionette.js

About Me (@yagudaev)

Co-founder of 0idle.com – an online marketplace for event organizers to find the perfect venue.

Built 0idle using Rails + Backbone.js

Entrepreneur and Rails Developer

Worked with Node.js

Started Winnipeg.js User Group

Consulting/Freelance work

Why Backbone.js? Simple

Flexible – use only the parts you need

Easy to integrate into existing code

Provides structure to your app

Proven

Well Documented (for the most part )

Good for hybrid apps

5

A Pain in the Back... Due to its un-opinionated approach, backbone

can be a real pain in the a**.

No clear guidelines

Lots of boilerplate code

Does not provide helpers to solve common problems

Easy to get memory leaks by not being careful when using events (zombie views)

Underline Principle for this talk

In any web system, the server should have the final say

Therefore, lets start developing server-side code first

Server-side code is easier to test (simple request/response)

Client-side functionality is to be considered as an added bonus in agile software development

You can do without backbone when you first start a project (KISS)

Assumption: a team of one

7

Design PatternsLets start simple and work our way up...

Scoping Your Selectors Problem: Overly generic jQuery selectors can

cause unexpected behavior when adding a new feature to a particular area of an application.

Example:

$(‘.btn-add’).click(addNewReply);

// Will conflict with:

$(‘.btn-add’).click(addNewMessage);

Solution: Use a backbone View

var MessageView = Backbone.View.exend({    el: $('#messages-page'),    events: { 'click .btn-add': 'addMessage' },    addMessage: function(ev) {        $target = $(ev.currentTarget)        // code to add message    }}); var RepliesView = Backbone.View.exend({    el: $('#replies-page'),    events: { 'click .btn-add': 'addReply' },    addMessage: function(ev) {        $target = $(ev.currentTarget)        // code to add message    }}); $(function() {    repliesView = new RepliesView();    messagesView = new MessagesView();});

File Structure Problem: Your one javascript file becomes long and

hard to maintain. You need a better way to separate concerns.

Solution: Break down project into folders based on object types in backbone with a main.js file that acts as the entry point of the application and defines a namespace.

Notes: you will need to use a make or a make like solution to stitch the files together (e.g. Rails asset pipeline or h5bp build tool).

Note 2: Keep it simple. Avoid using an AMD loader like RequireJS at this stage.

|____application.js -- manifest file (can have more than one)|____collections/|____lib/|____main.js|____models/|____templates/|____views/| |____messages_view.js| |____replies_view.js|____vendor/| |____backbone.js| |____jquery.js| |____underscore.js

File Structure (folders)

File Structure (manifest)In Rails:

//= require jquery//= require underscore//= require backbone//= require main//= require_directory ./models//= require_directory ./lib//= require_directory ./collections//= require_directory ./views

File Structure (files)window.APP_NAME = {    Models: {},    Collections: {},    Views: {},    Lib: {}  };

main.js -

views/messages_view.js -

APP_NAME.Views.MessagesView = Backbone.View.extend({...});

Template

Templates Problem: You need to dynamically generate

html content, but do not want pollute your js code with html markup.

Example: a dynamic file uploader allowing the user to upload any number of files.

Solution: place your html inside a script tag and use jQuery to extract the content of the template and then render it to the page. Alternatively you can use JST to give each template a separate file.

16

Templates<script id="photo-thumbnail-template" type="text/template">  <div class="thumbnail"> <img src="{{image_url}}" />    {{title}}  </div></script>

$thumbnail = $(Mustache.to_html($("#photo-thumbnail- template").html(), photo))$thumbnail.appendTo('.photos-preview.thumbnails')

17

Template Sharing

Template Sharing Problem: You have a template you want

rendered both on the client and server.

Example: you have a photo uploader that lets the user upload multiple photos and see photos that were already uploaded.

Solution: refactor the template into a mustache template and provide access to it from both the client and the server.

Limitation: cannot use helper methods (unless you are using Node.js)

19

Template Sharing//..<%- @photos.each do |photo| ->  <%= render 'photo_item', mustache: photo %><%- end %> //.. <script type="text/template" id="photo-item-template">  <%= render 'photo_item' %></script>

Client-side View Injection

Client-side View Injection

Problem: Similar to Template Sharing, but you are interested in re-using server-side helpers and do not care about filling in information in the template.

Example: a survey application which displays existing questions and allows to dynamically add new questions.

Solution: pre-render a partial somewhere in the DOM and to make it accessible to the client side code.

22

Client-side View Injection

// ...addSpace: function() {  var html = this.$('.btnAdd').data('view');  $('body').append(html);}// ...

<a href="#" class="btnAdd" data-view="<%= render 'space_card' %>">  Add Space</a>

View Helpers Problem: You want to change the format in

which your information is displayed in the view, but the logic will be too complicated to be added directly in the view.

Example: Format a date.

Solution: Define a view helper to handle this functionality.

24

// app/javascripts/helpers/application_helper.js

function formatDate(date) {

    moment(date).format("MMM Do YY");

// if using handlebars

Handlebars.registerHelper("formatDate", formatDate);

 

// Usage (in the templates):

{{formatDate date}}

Bootstrap Data Problem: You need to share data between the

client-side code and the server code. At the same time you would like to avoid incurring the cost of another HTTP request.

Example: You want to check if a user is logged in before you allow them to IM other users.

Solution: Use server-side processing to initialize your model(s).

26

<script>(function() {  var user;  // preprocessed file to bootstrap variables from ruby to javascript  window.ZI = {    Config: {},    Models: {},    Collections: {},    Views: {},    Lib: {},     currentUser: function() {      var user_data = <%=raw current_user ? current_user.to_json : 'null' %>;      if (user_data) return user = user || new ZI.Models.User(user_data);      return null;    }  };   // ... after the model has been loaded ...  console.log(ZI.currentUser() === null); // not logged in  console.log(ZI.currentUser() !== null); // logged in})();</script>

27

Bootstrap Data (e.g. 2)

  var photos = new Backbone.Collection;  photos.reset(<%= @photos.to_json %>);

Mixin Problem: You have code that is common

between several views or models, but it does not make sense to move code into a parent class.

Example: Code that allows you to open a message dialog and can be opened from several different views.

Solution: Use a mixin to include the common code in the target views or models.

29

ZI.Mixins.Navigation = {  openMessageDialog: function(ev) {    ev.preventDefault();    return $('#message-modal').modal();  }}; App.Views.VenuesView = Backbone.View.extend(  _.extend({}, App.Mixins.Navigation, {      //..})); App.Views.MessagesView = Backbone.View.extend(  _.extend({}, App.Mixins.Navigation, {      //...}));

30

Parent-Child (Sub Views) Views

Problem: You have a view that needs to communicate with another view and can be thought of as logically containing that view.

Example: Updating a dropdown after a user creates a new item through a modal dialog.

Solution: Create a reference to the child view from the parent view and listen on events fired by the child view.

32

ZI.Views.NewVenueModalView = Backbone.View.extend({  el: $('#new-venue-modal'),  events: { 'click .btn-primary': 'save' },  initialize: function() {    return this.formView = new ZI.Views.VenueFormView();  },  save: function() {    var _this = this;    return this.formView.save({      success: function() {        _this.trigger('save', _this.formView.model);        return _this.close();      },      error: function() {        return _this.trigger('error', arguments);      }    });  },  open: function() { return this.$el.modal(); },  close: function() { return this.$el.modal('hide'); }});// singleton pattern ZI.Views.NewVenueModalView.getInstance = function() {  return this.instance != null ? this.instance : this.instance = new this;};

Child View

33

Parent View

var openNewVenueDialog = function(e, data) {  var new_venue_modal_view = ZI.Views.NewVenueModalView.getInstance();  new_venue_modal_view.on('save', function(model) { $('#space_venue_id').append("<option>" + (model.get('name')) + "</ option>");    $('#space_venue_id').val(model.get('id'));  });  new_venue_modal_view.open();};

34

Two-Way Binding

Two-Way Bindings Problem: You need to dynamically and

automatically update a UI element when the underling data changes.

Example: Recalculate and display the total amount in a shopping cart if the quantity of any of the items changes.

Solution: Use a two-way binding plugin for backbone called backbone.stickit.

36

ZI.Views.EventDetailsBookView = Backbone.View.extend({  el: $('#event-details-booking-page'),  model: new Booking(),  bindings: {    '.total-price': {      observe: ['start_date', 'start_time', 'end_date', 'end_time'],      onGet: function(values) {        return '$' + this.calculateHoursBooked(values) * this.hourly_price;      }    },    '#booking_start_date': 'start_date', '#booking_end_date': 'end_date',    '#booking_start_time': 'start_time', '#booking_end_time': 'end_time'  },  initialize: function() {    this.hourly_price = this.$el.data('hourly-price');    return this.stickit();  },  calculateHoursBooked: function(values) {//...  }});

Marionette.js Provides architectural infrastructure for

backbone.

View Management Layouts

Regions

Specialized View Types

Memory management

Messaging

Application - controllers, modules, etc

38

QUESTIONS

References Railscasts -

http://railscasts.com/episodes/196-nested-model-form-revised

Backbone Patterns: http://ricostacruz.com/backbone-patterns/

Backbone.js - http://backbonejs.org/

Marionette - http://marionettejs.com/

Backbone.Stickit - http://nytimes.github.io/backbone.stickit/

40

References Handlebars - http://handlebarsjs.com/

Mustache - http://mustache.github.io/