Search code examples
javascriptjquerybackbone.jsbackbone-viewsbackbone-routing

Backbone Multi Step Modal


I'm creating a Twitter Bootstrap multi-step modal.

This is how I'm trying to get the workflow to work:

  1. User clicks on button to open modal... modal opens
  2. User types in access code, clicks next
  3. Server checks if access code exists. If it exists user moves on to next step in the modal, otherwise user receives an error.
  4. User enter's billing information. Price that the user will pay is based off the access code that they originally entered.
  5. Modal displays a confirmation message of the transaction and the access code

Here is my modal markup:

  section#paymentModal.modal.fade(tabindex='-1', role='dialog' aria-labelledby='paymentModalLabel' aria-hidden='true')
    div.modal-dialog
      div#paymentModalContent.modal-content
        div.modal-header
          button.close(data-dismiss='modal', aria-label='Close')
            span(aria-hidden="true")×
          h4#paymentModalLabel.modal-title Please enter your access code
        div#paymentModalBody.modal-body
        div.modal-footer
          button.btn-previous.btn.btn-warning.hide(type='button', data-orientation='previous') Previous
          button.btn-next.btn.btn-primary(type='button', data-orientation='next') Next Step

Here are my backbone templates:

  script(type="text/template", id="tmpl-accessCode")
    div.modal-header
      button.close(data-dismiss='modal', aria-label='Close')
        span(aria-hidden="true")×
      h4#paymentModalLabel.modal-title Please enter your access code
    div#paymentModalBody.modal-body
      input(type='text', placeholder='Enter Access Code')
    div.modal-footer
      button.btn-next.btn.btn-primary(type='button', data-orientation='next') Next Step

  script(type="text/template", id="tmpl-payment")
    div.modal-header
      button.close(data-dismiss='modal', aria-label='Close')
        span(aria-hidden="true")×
      h4#paymentModalLabel.modal-title Please enter your billing information
    div#paymentModalBody.modal-body
      // billing information goes here
    div.modal-footer
      button.btn-previous.btn.btn-warning.hide(type='button', data-orientation='previous') Previous
      button.btn-next.btn.btn-primary(type='button', data-orientation='next') Next Step

Here is my JS:

(function() {
  'use strict';

  app = app || {};

  app.AccessCode = Backbone.Model.extend({
    url: '/accessCode/'
  });

  app.Payment = Backbone.Model.extend({
    url: '/payment/'
  });

  app.AccessCodeView = Backbone.View.extend({
    el: '#paymentModalContent',
    template: _.template( $('#tmpl-accessCode').html() ),
    events: {
      'click .btn-next': 'verifyAccessCode'
    },
    initialize: function() {
      this.model = new app.AccessCode();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes ));
    },
    verifyAccessCode: function() {
      var accessCode = this.model.set({'name': 'thomas'});
      accessCode.fetch({
        success: function() {
          // chance view to billing view
        },
        error: function() {
          console.log('Access code doesn\'t exist');
        }
      });
    }
  });

  app.BillingView = Backbone.View.extend({
    el: '#paymentModalContent',
    template: _.template( $('#tmpl-payment').html() ),
    initialize: function() {
      this.model = new app.Payment();
      this.listenTo(this.model, 'sync', this.render);
      this.render();
    },
    render: function() {
      this.$el.html(this.template( this.model.attributes ));
    }  
  });

  $(document).ready(function() {
    app.accessCodeView = new app.AccessCodeView();
  });
}());

My question is how do I call BillingView from the success function callback in AccessCodeView? Doesn't it go against backbone principles to call a view from within another view?

Also, is there a better way for me to do this than the current approach I'm taking?

All help and advice is appreciated :)


Solution

  • A better way to do this is to decouple data loading and view creation. This can be effectively done by using Backbone.Events.

    1. Create a Custom JS Object say AppController and have it extend Backbone.Events.

      var AppController = _.extend({},Backbone.Events);

    2. Define a custom event and an event handler that will display load data and display a view.

      AppController.on("app:view_billing",function(payload){
         payload.accessCode.fetch().done(function() {
             // Create an instance of the billing view.
            // Update the url if required using the navigate method with trigger:false
         });
      });
      
    3. From your method now you can do this :

      verifyAccessCode: function(e) {
        var accessCode = this.model.set({'name': 'thomas'});
        var payLoad = {accessCode:this.accessCode};
        AppController.trigger("app:view_billing",payLoad);
      }