Skip to content

Releases: dmytroyarmak/marionette-contact-manager

Step 9 - Store data in localStorage

05 Feb 15:46
Compare
Choose a tag to compare
  1. Install Backbone.localStorage

    bower install Backbone.localStorage --save
    
  2. Add script in index.html

      <script src="vendor/Backbone.localStorage/backbone.localStorage.js"></script>
    
  3. Add localStorage to ContactManager.Collections.Contacts

    ContactManager.Collections.Contacts = Backbone.Collection.extend({
      model: ContactManager.Models.Contact,
      localStorage: new Backbone.LocalStorage('contacts')
    });
    
  4. Start app without sample data

      <script>
        $(function() {
          ContactManager.start();
        });
      </script>
    
    var contacts = new ContactManager.Collections.Contacts(),
    
  5. Fetch collection in controller

      this._contacts.fetch();
    
  6. Create sample data if collection is empty

      if (this._contacts.isEmpty()) {
        this._createSampleData();
      }
    
    _createSampleData: function() {
      _.each([
        {
          id: 1,
          name : 'Terrence S. Hatfield',
          tel: '651-603-1723',
          email: '[email protected]',
          avatar: '1.jpg'
        },
        {
          id: 2,
          name : 'Chris M. Manning',
          tel: '513-307-5859',
          email: '[email protected]',
          avatar: '2.jpg'
        },
        {
          id: 3,
          name : 'Ricky M. Digiacomo',
          tel: '918-774-0199',
          email: '[email protected]',
          avatar: '3.jpg'
        },
        {
          id: 4,
          name : 'Michael K. Bayne',
          tel: '702-989-5145',
          email: '[email protected]',
          avatar: '4.jpg'
        },
        {
          id: 5,
          name : 'John I. Wilson',
          tel: '318-292-6700',
          email: '[email protected]',
          avatar: '5.jpg'
        },
        {
          id: 6,
          name : 'Rodolfo P. Robinett',
          tel: '803-557-9815',
          email: '[email protected]',
          avatar: '6.jpg'
        }], function(contact) {
          this._contacts.create(contact);
        }, this);
    }
    
  7. Use destroy instead of remove

    this.listenTo(contactsView, 'itemview:delete:clicked', function(contactView) {
      contactView.model.destroy();
    });
    
  8. Use save instead of set

      this.listenTo(editContactForm, 'form:submitted', function(attrs) {
        contact.save(attrs);
        this.showContacts();
      });
    
  9. Remove avatar generator from ContactManager.Models.Contact

  10. Change contact creation code

      this.listenTo(newContactForm, 'form:submitted', function(attrs) {
        attrs.avatar = _.random(1, 15) + '.jpg';
        this._contacts.create(attrs);
        this.showContacts();
      });
    

Difference

Step 8 - Remove dependency to router

05 Feb 12:01
Compare
Choose a tag to compare
  1. Do not trigger routes when navigate in ContactManager.Controller
    instead of

    this._router.navigate('contacts', true);
    

    use

    this._router.navigate('contacts');
    this.showContacts();
    
  2. In ContactManager.Views.Contacts on click "Add Contact" button trigger event instead of change url:

    add class in template

        <p class="text-center">
          <a href="#contacts/new" class="btn btn-lg btn-outline add-contact-btn">Add Contact</a>
        </p>
    
    

    add triggers object to view

      triggers: {
        'click .add-contact-btn': 'addContact:clicked'
      }    
    
    
  3. Listen to addContact:clicked in controller

      this.listenTo(contactsView, 'addContact:clicked', function() {
        this._router.navigate('contacts/new');
        this.newContact();
      });
    
  4. In ContactManager.Views.ContactForm on click "Cancel" button trigger event instead of change url:
    add class in template

            <div class="col-sm-3">
              <a href="#contacts" class="btn btn-outline btn-lg btn-block form-cancel-btn">Cancel</a>
            </div>
    

    add triggers object to view

      triggers: {
        'click .form-cancel-btn': 'form:canceled'
      },
    
    
  5. Listen to form:canceled in controller

      this.listenTo(newContactForm, 'form:canceled', function() {
        this._router.navigate('contacts');
        this.showContacts();
      });
    
  6. Move this._router.navigate to end of corresponding methods

    showContacts: function() {
      /* ... */
      this._router.navigate('contacts');
    },  
    
    newContact: function() {
      /* ... */
      this._router.navigate('contacts/new');
    },  
    
    editContact: function(id) {
      /* ... */
    
        this._router.navigate('contacts/edit/' + id);
      } else {
        this.showContacts();
      }
    }
    
  7. In ContactManager.Views.Contact on click "Delete" button trigger event
    instead of:

    events: {
      'click .delete-contract': 'onClickDelete'
    },
    
    onClickDelete: function(e) {
      e.preventDefault();
      this.model.collection.remove(this.model);
    }
    

    use

    triggers: {
      'click .delete-contract': 'delete:clicked'
    }
    
  8. Listen to itemview:delete:clicked in controller

      this.listenTo(contactsView, 'itemview:delete:clicked', function(contactView) {
        this._contacts.remove(contactView.model);
      });
    
  9. In ContactManager.Views.Contact on click "Edit" button trigger event

              <a href="#contacts/edit/<%- id %>"><span class="glyphicon glyphicon-pencil edit-contract"></span></a>
    
    triggers: {
      'click .delete-contract': 'delete:clicked',
      'click .edit-contract': 'edit:clicked'
    }
    
  10. Listen to itemview:edit:clicked in controller

      this.listenTo(contactsView, 'itemview:edit:clicked', function(contactView) {
        this.editContact(contactView.model.id);
      });
    

Difference

Step 7 - Use AppRouter

05 Feb 11:20
Compare
Choose a tag to compare
  1. In router.js change Backbone.Router.extend to Marionette.AppRouter.extend

  2. Remove all routes except home

    ContactManager.Router = Backbone.Router.extend({
      routes: {
        '': 'home'
      }
    });
    
  3. Move home method from ContactManager.Controller to ContactManager.Router

    home: function() {
      this.navigate('contacts', {
        trigger: true,
        replace: true
      });
    }
    
  4. Bind contacts routes to router in application initialization

    ContactManager.addInitializer(function(data) {
      var contacts = new ContactManager.Collections.Contacts(data.contacts),
          router = new ContactManager.Router(),
          controller = new ContactManager.Controller({
            contacts: contacts,
            router: router,
            mainRegion: this.mainRegion
          });
    
      router.processAppRoutes(controller, {
        'contacts': 'showContacts',
        'contacts/new': 'newContact',
        'contacts/edit/:id': 'editContact'
      });
    });
    

Difference

Step 6 - Add Controller

05 Feb 10:48
Compare
Choose a tag to compare
  1. Create file controller.js and add in index.html

  2. Move all router's handlers from app initialization to controller

    ContactManager.Controller = Marionette.Controller.extend({
      initialize: function(options) {
        this._contacts = options.contacts;
        this._router = options.router;
        this._mainRegion = options.mainRegion;
      },
    
      home: function() {
        this._router.navigate('contacts', {
          trigger: true,
          replace: true
        });
      },
    
      showContacts: function() {
        var contactsView = new ContactManager.Views.Contacts({
          collection: this._contacts
        });
    
        ContactManager.mainRegion.show(contactsView);
      },
    
      newContact: function() {
        var newContactForm = new ContactManager.Views.ContactForm({
          model: new ContactManager.Models.Contact()
        });
    
        this.listenTo(newContactForm, 'form:submitted', function(attrs) {
          attrs.id = this._contacts.isEmpty() ? 1 : (_.max(this._contacts.pluck('id')) + 1);
          this._contacts.add(attrs);
          this._router.navigate('contacts', true);
        });
    
        ContactManager.mainRegion.show(newContactForm);
      },
    
      editContact: function(id) {
        var contact = this._contacts.get(id),
            editContactForm;
    
        if (contact) {
          editContactForm = new ContactManager.Views.ContactForm({
              model: contact
          });
    
          this.listenTo(editContactForm, 'form:submitted', function(attrs) {
            contact.set(attrs);
            this._router.navigate('contacts', true);
          });
    
          ContactManager.mainRegion.show(editContactForm);
        } else {
          this._router.navigate('contacts', true);
        }
      }
    });
    
    
  3. Crate controller on application initialization and bind to router

    ContactManager.addInitializer(function(data) {
      var contacts = new ContactManager.Collections.Contacts(data.contacts),
          router = new ContactManager.Router(),
          controller = new ContactManager.Controller({
            contacts: contacts,
            router: router,
            mainRegion: this.mainRegion
          });
    
    
      router.on('route:home', controller.home, controller);
      router.on('route:showContacts', controller.showContacts, controller);
      router.on('route:newContact', controller.newContact, controller);
      router.on('route:editContact', controller.editContact, controller);
    });
    

Difference

Step 5 - Use Application

05 Feb 09:43
Compare
Choose a tag to compare
  1. Let ContactManager in app.js be instance of Marionette.Application instead of simple object

    var ContactManager = new Marionette.Application({
      Models: {},
      Collections: {},
      Views: {}
    });
    
  2. Use addInitializer instead of method start

    ContactManager.addInitializer(function(data) {
      ...
    });
    
  3. Add mainRegion to ContactManager

    ContactManager.addRegions({
      mainRegion: '.main-container'
    });
    

    and use as:

      ContactManager.mainRegion.show(newContactForm);
    
  4. Move Backbone.history.start() to initialize:after callback

    MyApp.on('initialize:after', function(options){
      if (Backbone.history){
        Backbone.history.start();
      }
    });
    

Difference

Step 4 - Use Region

05 Feb 08:59
Compare
Choose a tag to compare
  1. Create mainRegion in start method of app.js

    var mainRegion = new Marionette.Region({
    el: ".main-container"
    });  
    
  2. Instead of change html of .main-container use region's show method

    Change

    $('.main-container').html(contactsView.render().$el);
    ...
    $('.main-container').html(newContactForm.render().$el);
    ...
    $('.main-container').html(editContactForm.render().$el);
    

    to

    mainRegion.show(contactsView);
    ...
    mainRegion.show(newContactForm);
    ...
    mainRegion.show(editContactForm);
    

Difference

Step 3 - Use CompositeView

05 Feb 08:32
Compare
Choose a tag to compare
  1. In views/contacts.js change Backbone.View.extend to 'Marionette.CompositeView.extend'

  2. Instead of

    template: _.template($('#tpl-contacts').html()),
    

    use just

    template: '#tpl-contacts',
    
  3. Remove renderOne and render methods

  4. Add itemView and itemViewContainer

    itemView: ContactManager.Views.Contact,
    itemViewContainer: '.contacts-container'
    
  5. Remove modelEvents from ContactManager.Views.Contact

Difference

Step 2 - Use ItemView

05 Feb 08:19
Compare
Choose a tag to compare
  1. Use ItemView in views/contactForm.js:
    1. Change Backbone.View.extend to 'Marionette.ItemView.extend'

    2. Instead of

      template: _.template($('#tpl-new-contact').html()),
      

      use just

      template: '#tpl-new-contact',
      
    3. Remove render method

    4. Add serializeData method

      serializeData: function() {
        return _.extend(this.model.toJSON(), {
          isNew: this.model.isNew()
        });
      },
      
    5. Use ui to organize UI elements

      ui: {
      nameInput: '.contact-name-input',
      telInput: '.contact-tel-input',
      emailInput: '.contact-email-input'
      },
      
      this.trigger('form:submitted', {
        name: this.ui.nameInput.val(),
        tel: this.ui.telInput.val(),
        email: this.ui.emailInput.val()
      });
      
  2. Use ItemView in views/contact.js:
    1. Change Backbone.View.extend to 'Marionette.ItemView.extend'

    2. Instead of

      template: _.template($('#tpl-contact').html()),
      

      use just

      template: '#tpl-contact',
      
    3. Remove render method

    4. Check in browser

    5. Instead of

      initialize: function() {
        this.listenTo(this.model, 'remove', this.remove);
      },
      

      use modelEvents

      modelEvents: {
        'remove': 'close'
      },
      

Difference

Step 10 - Store data in RESTful backend

05 Feb 15:47
Compare
Choose a tag to compare
  1. Remove backbone.localStorage

  2. Add url to collection

    ContactManager.Collections.Contacts = Backbone.Collection.extend({
    model: ContactManager.Models.Contact,
    url: 'contacts'
    });
    
  3. Add ajaxPrefilter to app.js

    ContactManager.addInitializer(function() {
      $.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
        options.url = 'http://contact-manager-api.herokuapp.com/' + options.url;
      });
    });
    
    
  4. Make sure that collection is fetched before any action

    ContactManager.Controller = Marionette.Controller.extend({
      initialize: function(options) {
        this._router = options.router;
        this._mainRegion = options.mainRegion;
        this._contacts = options.contacts;
      },
    
      showContacts: function() {
        this._fetchContacts().then(_.bind(this.showContactsSync, this));
      },
    
      showContactsSync: function() {
        var contactsView = new ContactManager.Views.Contacts({
          collection: this._contacts
        });
    
        this.listenTo(contactsView, 'addContact:clicked', this.newContactSync);
        this.listenTo(contactsView, 'itemview:delete:clicked', function(contactView) {
          contactView.model.destroy();
        });
        this.listenTo(contactsView, 'itemview:edit:clicked', function(contactView) {
          this.editContactSync(contactView.model.id);
        });
    
        ContactManager.mainRegion.show(contactsView);
    
        this._router.navigate('contacts');
      },
    
      newContact: function() {
        this._fetchContacts().then(_.bind(this.newContactSync, this));
      },
    
      newContactSync: function() {
        var newContactForm = new ContactManager.Views.ContactForm({
          model: new ContactManager.Models.Contact()
        });
    
        this.listenTo(newContactForm, 'form:submitted', function(attrs) {
          attrs.avatar = _.random(1, 15) + '.jpg';
          this._contacts.create(attrs, {wait: true});
          this.showContactsSync();
        });
    
        this.listenTo(newContactForm, 'form:canceled', this.showContactsSync);
    
        ContactManager.mainRegion.show(newContactForm);
    
        this._router.navigate('contacts/new');
      },
    
      editContact: function(id) {
        this._fetchContacts().then(_.bind(this.editContactSync, this, id));
      },
    
      editContactSync: function(id) {
        var contact = this._contacts.get(id),
            editContactForm;
    
        if (contact) {
          editContactForm = new ContactManager.Views.ContactForm({
              model: contact
          });
    
          this.listenTo(editContactForm, 'form:submitted', function(attrs) {
            contact.save(attrs);
            this.showContactsSync();
          });
    
          this.listenTo(editContactForm, 'form:canceled', this.showContactsSync);
    
          ContactManager.mainRegion.show(editContactForm);
    
          this._router.navigate('contacts/edit/' + id);
        } else {
          this.showContacts();
        }
      },
    
      _fetchContacts: function() {
        this._contactsFetching = this._contactsFetching || this._contacts.fetch();
        return this._contactsFetching;
      }
    });
    

Difference

Step 1 - Install Marionette.js

05 Feb 07:25
Compare
Choose a tag to compare

Step 1

  1. Install marionette using bower:

    bower install marionette --save
    
  2. Add script to index.html:

      <script src="vendor/marionette/lib/backbone.marionette.js"></script>
    
  3. Check in DevTools

Difference