Putting it all together

We have created four simple but powerful new views types that can be used easily on projects, minimizing the effort and making less redundant code. In the next section, we will convert our contacts project into a more complex project, using what we have learned here:

Putting it all together

Figure 2.4: Application root layout

Our application will have a root layout with three sections:

  • Header – Will contain a navigation bar
  • Footer – Copyright information
  • Main – This element shows all sub-applications on demand

This layout description is not a Layout object; instead, it describes the HTM root content:

<!doctype html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <title>mastering backbone design</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="shortcut icon" href="/favicon.ico">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->

    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/sweetalert.css">
    <link rel="stylesheet" href="css/pnotify.custom.min.css">
    <link rel="stylesheet" href="css/font-awesome.min.css">
    <link rel="stylesheet" href="css/main.css">
  </head>
  <body>
    <!--[if lt IE 10]>
      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
    <![endif]-->

    <nav class="navbar">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="#">
            Mastering Backbone.js
          </a>
        </div>
      </div>
    </nav>

    <div id="main" class="container"></div>

    <script src="js/vendor/jquery-2.1.4.min.js"></script>
    <script src="js/vendor/bootstrap.min.js"></script>
    <script src="js/vendor/sweetalert.min.js"></script>
    <script src="js/vendor/pnotify.custom.min.js"></script>
    <script src="js/vendor/underscore-min.js"></script>
    <script src="js/vendor/backbone-min.js"></script>

  </body>
</html>

In this root layout, the header and footer are very straightforward, so is not necessary to create a separate view for them. There is a main div that will be our main region, for the whole application.

Showing a list

The ContactList subapplication is responsible for rendering the collection in the DOM. Thus, the ContactList object will instantiate the necessary views:

// apps/contacts/contactList.js
showList(contacts) {
  // Create the views
  var layout = new ContactListLayout();
  var actionBar = new ContactListActionBar();
  var contactList = new ContactListView({collection: contacts});

  // Show the views
  this.region.show(layout);
  layout.getRegion('actions').show(actionBar);
  layout.getRegion('list').show(contactList);

  this.listenTo(contactList, 'item:contact:delete',
    this.deleteContact);
}

A layout is created to put the CollectionView inside; the layout template has a div with a contact-list-layout id that will be used as the target region:

// index.html
<script id="contact-list-layout" type="text/template">
  <div class="actions-bar-container"></div>
  <div class="list-container"></div>
  <div class="footer text-muted">
    © 2015. <a href="#">Mastering Backbone.js</a> by <a href="https://twitter.com/abieealejandro" target="_blank">Abiee Alejandro</a>
  </div>
</script>

And the layout code is very simple:

// apps/contacts/contactList.js
class ContactListLayout extends Layout {
  constructor(options) {
    super(options);
    this.template = '#contact-list-layout';
    this.regions = {
      actions: '.actions-bar-container',
      list: '.list-container'
    };
  }

  get className() {
    return 'row page-container';
  }
}

The view that renders the collection of contacts is very straightforward because it just need to specify the modelView attribute:

// apps/contacts/contactList.js
class ContactListView extends CollectionView {
  constructor(options) {
    super(options);
    this.modelView = ContactListItemView;
  }

  get className() {
    return 'contact-list';
  }
}

The contact card template shows the contact name, phone number, email, and its social networks:

// index.html
<script id="contact-list-item" type="text/template">
  <div class="box thumbnail">
    <div class="photo">
      <img src="http://placehold.it/250x250"
        alt="Contact photo" />
      <div class="action-bar clearfix">
        <div class="action-buttons pull-right">
          <button id="delete"
            class="btn btn-danger btn-xs">delete</button>
          <button id="view"
            class="btn btn-primary btn-xs">view</button>
        </div>
      </div>
    </div>
    <div class="caption-container">
      <div class="caption">
        <h5><%= name %></h5>
        <% if (phone) { %>
          <p class="phone no-margin"><%= phone %></p>
        <% } %>
        <% if (email) { %>
          <p class="email no-margin"><%= email %></p>
        <% } %>
        <div class="bottom">
          <ul class="social-networks">
            <% if (facebook) { %>
            <li>
              <a href="<%= facebook %>" title="Google Drive">
                <i class="fa fa-facebook"></i>
              </a>
            </li>
            <% } %>
            <% if (twitter) { %>
            <li>
              <a href="<%= twitter %>" title="Twitter">
                <i class="fa fa-twitter"></i>
              </a>
            </li>
            <% } %>
            <% if (google) { %>
            <li>
              <a href="<%= google %>" title="Google Drive">
                <i class="fa fa-google-plus"></i>
              </a>
            </li>
            <% } %>
            <% if (github) { %>
            <li>
              <a href="<%= github %>" title="Github">
                <i class="fa fa-github"></i>
              </a>
            </li>
            <% } %>
          </ul>
        </div>
      </div>
    </div>
  </div>
</script>

The ContactListItemView class should handle the delete and view events:

// apps/contacts/contactList.js
class ContactListItemView extends ModelView {
  constructor(options) {
    super(options);
    this.template = '#contact-list-item';
  }

  get className() {
    return 'col-xs-12 col-sm-6 col-md-3';
  }

  get events() {
    return {
      'click #delete': 'deleteContact',
      'click #view': 'viewContact'
    };
  }

  initialize(options) {
    this.listenTo(options.model, 'change', this.render);
  }

  deleteContact() {
    this.trigger('contact:delete', this.model);
  }

  viewContact() {
    var contactId = this.model.get('id');
    App.router.navigate(`contacts/view/${contactId}`, true);
  }
}

When the user clicks on the Delete button, the view triggers a contact:delete event and lets the controller handle the deletion process. Because the View button is simpler than the Delete, we can redirect the user to the contact list from the view; note that delegating this very simple task to the controller will add more overhead without benefit.

The action bar allow the user to add new users.

<script id="contact-list-action-bar" type="text/template">
  <button class="btn btn-lg btn-success">
    Create a new contact
  </button>
</script>

ContactListActionBar just renders its template and waits for a click on its button.

// apps/contacts/contactList.js
class ContactListActionBar extends ModelView {
  constructor(options) {
    super(options);
    this.template = '#contact-list-action-bar';
  }

  get className() {
    return 'options-bar col-xs-12';
  }

  get events() {
    return {
      'click button': 'createContact'
    };
  }

  createContact() {
    App.router.navigate('contacts/new', true);
  }
}

When the button is clicked, we redirect the user to the contact form to create a new user.

Showing the details

The contact details show a read-only version of a single contact; here you can see all the details of a given contact but no edition. The following screenshots shows how it looks:

Showing the details

Figure 2.5: Contact details

To show a read-only version of a contact, we need to first define a layout:

<script id="contact-view-layout" type="text/template">
  <div class="row page-container">
    <div id="contact-widget"
      class="col-xs-12 col-sm-4 col-md-3"></div>
    <div class="col-xs-12 col-sm-8 col-md-9">
      <div class="row">
        <div id="about-container"></div>
        <div id="call-log-container"></div>
      </div>
    </div>
  </div>
  <div class="footer text-muted">
    © 2015. <a href="#">Mastering Backbone.js</a> by <a href="https://twitter.com/abieealejandro" target="_blank">Abiee Alejandro</a>
  </div>
</script>

The layout defines two regions, one for the widget at the left and another one for the main content:

// apps/contacts/contactViewer.js
class ContactViewLayout extends Layout {
  constructor(options) {
    super(options);
    this.template = '#contact-view-layout';
    this.regions = {
      widget: '#contact-widget',
      about: '#about-container'
    };
  }

  get className() {
    return 'row page-container';
  }
}

When ContactViewLayout is rendered, the widget and the about information should be rendered. The templates for these views are very simple, so for space reasons will not be shown here; if you want to see the details of the implementation, please go to the GitHub repo for this book.

The ContactAbout view includes three buttons to go back to the list, another to delete the contact, and a final one to edit it.

// apps/contacts/contactViewer.js
class ContactAbout extends ModelView {
  constructor(options) {
    super(options);
    this.template = '#contact-view-about';
  }

  get className() {
    return 'panel panel-simple';
  }

  get events() {
    return {
      'click #back': 'goToList',
      'click #delete': 'deleteContact',
      'click #edit': 'editContact'
    };
  }

  goToList() {
    App.router.navigate('contacts', true);
  }

  deleteContact() {
    this.trigger('contact:delete', this.model);
  }

  editContact() {
    var contactId = this.model.get('id');
    App.router.navigate(`contacts/edit/${contactId}`, true);
  }
}

As we did in the ContactList, we will delegate the deletion process to the controller; views should not handle that business logic. However, the edit and go back button are simple URL redirections and can be implemented directly in the view.

Editing information

Figure 2.6 shows how the edit form for the contacts should looks. The form view should be able to grab information from the input boxes and update the Contact model passed to it.

A layout template should be created here to separate the widget at the left from the form view at the right:

<script id="contact-form-layout" type="text/template">
  <div id="preview-container"
    class="col-xs-12 col-sm-4 col-md-3"></div>
  <div id="form-container"
    class="col-xs-12 col-sm-8 col-md-9"></div>

  <div class="footer text-muted">
    © 2015. <a href="#">Mastering Backbone.js</a> by <a href="http://themeforest.net/user/Kopyov" target="_blank">Abiee Alejandro</a>
  </div>
</script>
Editing information

Figure 2.6: Edit contact form

The layout defines two regions:

// apps/contacts/contactEditor.js
class ContactFormLayout extends Layout {
  constructor(options) {
    super(options);
    this.template = '#contact-form-layout';
    this.regions = {
      preview: '#preview-container',
      form: '#form-container'
    };
  }

  get className() {
    return 'row page-container';
  }
}

In order to edit a contact, we need to define a form:

// index.html
<script id="contact-form" type="text/template">
  <div class="panel panel-simple">
    <div class="panel-heading">Edit contact</div>
    <div class="panel-body">
      <form class="form-horizontal">
        <div class="form-group">
          <label for="name"
            class="col-sm-2 control-label">Name</label>
          <div class="col-sm-10">
            <input id="name" type="text"
              class="form-control" placeholder="Full name"
              value="<%= name %>" />
          </div>
        </div>
        // ...
        
        <hr />
        
        <h4>Contact info</h4>
        <div class="form-group">
          <label for="name"
            class="col-sm-2 control-label">Phone</label>
          <div class="col-sm-10">
            <input id="name" type="text"
              class="form-control"
              placeholder="(123) 456 7890" value="<%= phone %>" />
          </div>
        </div>
        // ...
      </form>
    </div>
    <div class="panel-footer clearfix">
      <div class="panel-buttons">
        <button id="cancel" class="btn btn-default">Cancel</button>
        <button id="save" class="btn btn-success">Save</button>
      </div>
    </div>
  </div>
</script>

For space reasons, I have eliminated the duplicated code in the book, but you can see the complete code in the GitHub repo. Please note that this form will be used for editing and creating new contacts. For each attribute in the model, an input is rendered with the contents of the attribute:

// apps/contacts/contactEditor.js 
class ContactForm extends ModelView {
  constructor(options) {
    super(options);
    this.template = '#contact-form';
  }

  get className() {
    return 'form-horizontal';
  }

  get events() {
    return {
      'click #save': 'saveContact',
      'click #cancel': 'cancel'
    };
  }

  serializeData() {
    return _.defaults(this.model.toJSON(), {
      name: '',
      age: '',
      phone: '',
      email: '',
      address1: '',
      address2: ''
    });
  }

  saveContact(event) {
    event.preventDefault();
    this.model.set('name', this.getInput('#name'));
    this.model.set('phone', this.getInput('#phone'));
    this.model.set('email', this.getInput('#email'));
    this.model.set('address1', this.getInput('#address1'));
    this.model.set('address2', this.getInput('#address2'));
    this.model.set('facebook', this.getInput('#facebook'));
    this.model.set('twitter', this.getInput('#twitter'));
    this.model.set('google', this.getInput('#google'));
    this.model.set('github', this.getInput('#github'));
    this.trigger('form:save', this.model);
  }

  getInput(selector) {
    return this.$el.find(selector).val();
  }

  cancel() {
    this.trigger('form:cancel');
  }
}

When the user clicks on the Cancel button, it triggers a form:cancel event that is processed by the ContactEditor subapplication controller.

// apps/contacts/contactEditor.js 
  cancel() {
    // Warn user before make redirection to prevent accidental
    // cencel
    App.askConfirmation('Changes will be lost', isConfirm => {
      if (isConfirm) {
        App.router.navigate('contacts', true);
      }
    });
  }

When the model is rendered, it may or may not contain attributes due to the server response; for this reason, we extend the serializeData() method to assign default values.

When the user clicks on the Save button, the saveContact() is called, it gets the data from the inputs and assigns new values to the model, then triggers a form:save event to be processed by the ContactEditor subapplication controller.

// apps/contacts/edit-contact.js 
  saveContact(contact) {
    contact.save(null, {
      success() {
        // Redirect user to contact list after save
        App.notifySuccess('Contact saved');
        App.router.navigate('contacts', true);
      },
      error() {
        // Show error message if something goes wrong
        App.notifyError('Something goes wrong');
      }
    });
  }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.177.39