Docs

Everything You Need to Know

Tutorials

Organizing Your Code

It's About Parts

The part of ChocolateChip-UI that you will always use, regardless of the type of app you're making, is views. The second part that you'll probably need to use is a navigation list with routing. To better understand how you can organize your app code, you need to know how views work.

Views

Although we write code to make an app, when we run it, we see a UI. ChocolateChip-UI's views let you break that UI down into managable chunks. You can create views to do this in three ways. You can have a view render data to the screen. This might be just a snippet of text, or an object or a array of objects. You can also add event listeners to your views. You can also define the template the view uses when you setup the view. This results in a complete component where you do not need to look elsewhere to understand its functionality. Or you could create a view whose only purpose is to wire up events for the UI.

When your app has a lot of views, it gets hard to dig through the code to find the view you want. You can combine all of your views into one object as a namespace. Below we show a before and after:

//===========================
// Scope views to App object:
//===========================
var App = {

  // Define views:
  SpecialRedsView: $.View({
    element: '#picksRed',
    variable: 'wine'
  }),

  SpecialWhitesView: $.View({
    element: '#picksWhite',
    variable: 'wine'
  }),

  SelectedWineView: $.View({
    element: '#wineDetail',
    variable: 'wine'
  }),

  FilteredWinesView: $.View({
    element: '#filteredWines',
    variable: 'wine'
  }),

  WineryView: $.View({
    element: '#viewWinery',
    events: [{
      event: 'tap',
      callback: function() {
        var location = $(this).attr('data-location');
        window.location.href= 'http://maps.apple.com/?q=' + location;
      }
    }]
  })
};

// Render the views:
//==================
App.SpecialRedsView.render(bestWines[0].data);
App.SpecialWhitesView.render(bestWines[1].data);

In the above example, the last view, WineryView, is only for wiring up an event. That's why it has the property noTemplate. The other views are all callable off the App object. This gives you namespacing and one place to look for all of your views.

Another common problem with UIs are event registration. If you have a lot of interactive elements, such as buttons, etc., you can wind up with events dispersed throughout your code. It can be hard to find the event your looking for. In the following example, a screen had a lot of elements with registered events. We created a view for the screen and attached all those loose event there. When you add events to a view, ChocolateChip-UI uses event delegation based on the view's element. In this case its the screen.

// Create a view to register events:
//==================================
var AppEvents = $.View({
  element: 'screen',
  events: [
    // Add todo item:
    //===============
    {
      event: 'tap',
      element: '.add',
      callback: function() {
        var todo = $('#add-todo').val();
        $('#add-todo')[0].value = '';
        if (todo) {
          TodosData.unshift({state: 'active', value: todo});
          renderActiveTodos(TodosData);
          TodoView.render(TodosData);
          toggleButtonState('#show-all');
        }
        $.Box.set('chui-todos', TodosData, function(err, value) {});
      }
    },
    // Set state of todo:
    //===================
    {
      event: 'tap',
      element: '.set-state',
      callback: function() {
        var parent = $(this).closest('li');
        var index = parent.index();
        parent.toggleClass('active');
        var state = parent.hasClass('active') ? 'active' : 'completed';
        TodosData[index].state = state;
        renderActiveTodos(TodosData);
        $.Box.set('chui-todos', TodosData, function(err, value) {});
      }
    },
    // Delete a todo:
    //===============
    {
      event: 'tap',
      element: '.delete-item',
      callback: function() {
        var index = $(this).closest('li').index();
        // Remove item from list:
        TodosData.splice(index, 1);
        TodoView.render(TodosData);
        renderActiveTodos(TodosData);
        $.Box.set('chui-todos', TodosData, function(err, value) {});
      }
    },
    // Handle visibility of todo items by state:
    //==========================================
    {
      event: 'tap',
      element: 'button',
      callback: function() {
        var id = this.id;
        var todoItems = $('#todo-items li');
        switch(id) {
          // Show all todos:
          case 'show-all':
            todoItems.css(listItemFlex);
            toggleButtonState(this);
            break;
          // Show only active todos:
          case 'show-active':
            todoItems.hazClass('active').css(listItemFlex);
            todoItems.hazntClass('active').hide();
            toggleButtonState(this);
          break;
          // Show only completed todos:
          case 'show-completed':
            todoItems.hazClass('active').hide();
            todoItems.hazntClass('active').css(listItemFlex);
            toggleButtonState(this);
          break;
        }
      }
    }
  ]
});

Routes

The other main item you will use in your app is routes. You set up routes by creating a router object and defining routes on it. This means there's just one place to look for routes:

// Define Routes:
//===============
var FragranzRoutes = $.Router();
FragranzRoutes.addRoute([
  {
    route: 'fragranceList',
    callback: function(genre) {
      chosenGenre = [];
      fragrances.forEach(function(fragrance) {
        if (fragrance.genre === genre) {
          chosenGenre.push(fragrance)
        }
      });
      AvailableFragrancesView.render(chosenGenre);
      FragrancesGenreTitleView.render([genre]);
      $('#backToGenre span').text(genre);
    }
  },
  {
    route: 'detail',
    callback: function(sku) {
      var chosenFragrance = chosenGenre.filter(function(fragrance) {
        return fragrance.sku === sku;
      });
      FragranceDetailView.render(chosenFragrance);
      DetalTitleView.render([chosenFragrance[0].product_title])
      $('#addToCart').data('fragrance', chosenFragrance[0]);
    }
  }
]);

Mediators

If you want to decouple your code, you'll use mediators. However, you'd probably never have more than a couple. You can namespace them with views or routes or however you like.

Create Namespaces Based on Functionality

If you have a complex app with different things going on, create separate namespaces for each division. For example, Registration, ShoppingCart, etc. Add all the relavant code to the namespace. That way all the code related to a block of functionality is in one place. Below is a Shopping Cart namespace:

var Cart = {

  CartModel: $.Model([], 'cart-model');

  CartView: $.View({
    element: '#purchaseItems',
    model: CartModel,
    variable: 'item'
  }),

  TotalItemsView: $.View({
    element: '#totalItems',
    variable: 'total'
  }),

  TotalCostView: $.View({
    element: '#totalCost',
    variable: 'total'
  }),

  TotalPurchasedItemsViews: $.View({
    element: '#purchaseDetails',
    variable: 'item',
    model: CartModel
  }),

  TotalPurchaseCostView: $.View({
    element: '#confirmTotalCost',
    variable: 'cost'
  }),

  CartEvents: $.View({
    element: '#cart',
    events: [
      {
        event: 'tap',
        element: '#cancelOrder',
        callback: function() {
          Cart.CartModel.purge();
          Cart.TotalItemsView.empty();
          Cart.TotalCostView.empty();
          Cart.CartView.empty();
          $.GoBackToScreen('main');
        })
        }
      },
      {
        event: 'tap',
        element: '#placeOrder',
        callback: function() {
          $.GoToScreen('confirmation');
          Cart.TotalPurchasedItemsViews.render();
          function confirmationNumber() {
            var d = Date.now();
            var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split('');
            var randomLetter = charset[Math.floor(Math.random() * charset.length)];
            return randomLetter + 'xx-xxxx-xxx'.replace(/[xy]/g, function(c) {
              var r = (d + Math.random() * 16) % 16 | 0;
              d = Math.floor(d / 16);
              return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
            });
          }
          $('#confirmationNum').text(confirmationNumber());
        }
      },
      {
        event: 'tap',
        element: '#backToCart',
        callback: function() {
          Cart.CartModel.purge();
          Cart.TotalItemsView.empty();
          Cart.TotalCostView.empty();
          Cart.CartView.empty();
        }
      }
    ]
  });
};

Build Scripts

You could also break your code down into separate files. Using a logical file structure allows you to have small, managable chunks of code. It's easy to find cart code in a cart directory. If you take this approach, you will need to do one of two things. Create a build script to combine them together, or use a module loader/package manager, which will do that for you. There are so many module bundlers to choose from: SystemJS, JSPM, Webpack, etc. We feel that a build script gives you more control over how your final file is. In contrast, bundlers add module loading code in the final output. If you choose to write a build script, there are a number of ways to do so. You could use make, npm, gulp or grunt. We currently use gulp to assemble all the pieces of ChocolateChipJS together and are happy with how it's working.

ES6

If you really want to be able to organize your app in a modular way, then you will want to build your app using ES6 modules.

To see examples of projects built around ES6 modules, you should examine ChocolateChip-UI's reference apps. You'll need to have our command line tool install to get them. Run:

chui -r

This will create a folder called 'Chui Reference Apps' on your desktop. Inside you'll find a folder called jspm. In this you'll find for projects. In each of these you'll find a dev folder. These hold the projects view, models, routers and controllers broken down as ES6 modules. Each project is organized differently to show you various possible was of structing a project.