Data Stores

Basics - Ajax

Basics

When you develop something larger than a tiny widget, you are recommended to create a data store for your Cape.JS component.

The following example illustrates the basic concept of data stores.

index.html

<div id="todo-list"></div>

<script src="./todo_item_store.js"></script>
<script src="./todo_list2.js"></script>
<script>
  var todoList = new TodoList2();
  todoList.mount('todo-list');
</script>

todo_item_store.js

var TodoItemStore = Cape.createDataStoreClass({
  init: function() {
    this.items = [
      { title: 'Foo', done: false },
      { title: 'Bar', done: true }
    ];
    this.propagate();
  },
  addItem: function(title) {
    this.items.push({ title: title, done: false });
    this.propagate();
  },
  toggle: function(item) {
    item.done = !item.done;
    this.propagate();
  }
});

The TodoItemStore class has three methods and each of them ends with this.propagate(), which calls the refresh method of all attached components.

todo_list2.js

var TodoList2 = Cape.createComponentClass({
  render: function(m) {
    m.ul(function(m) {
      this.ds.items.forEach(function(item) {
        this.renderItem(m, item);
      }.bind(this))
    });
    this.renderForm(m);
  },

  renderItem: function(m, item) {
    m.li(function(m) {
      m.label({ class: { completed: item.done }}, function(m) {
        m.onclick(function(e) { this.ds.toggle(item) })
          .checked(item.done).input({ type: 'checkbox' })
        m.text(item.title);
      })
    })
  },

  renderForm: function(m) {
    m.onsubmit(function(e) { this.addItem(); return false; });
    m.formFor('item', function(m) {
      m.onkeyup(function(e) { this.refresh() }).textField('title');
      m.onclick(function(e) { this.addItem() })
        .disabled(this.val('item.title') === '').btn("Add");
    });
  },

  init: function() {
    this.ds = TodoItemStore.create();
    this.ds.attach(this);
    this.ds.init();
  },

  addItem: function() {
    this.ds.addItem(this.val('item.title', ''))
  },

  beforeUnmount: function() {
    this.ds.detach(this);
  }
});

Within the init method, we create a singleton instance of TodoItemStore class (data store), and set it to the ds property of this component.

Then we call the attach method of the data store to register this component as a listener to the change event. When the content of data store is changed, a change event is emitted to this component.

When we click a check box, the following code is executed:

this.ds.toggle(item)

This inverts the done attribute of this item and calls this.propagate(), which will cause the re-rendering of this component.

A working demo is found at https://github.com/capejs/capejs/tree/master/demo/todo_list2

Ajax

In this section, we assume that an API server is running behind. When we access it with GET /api/items.json, it responds with a JSON string such as [{ title: 'Foo', done: false },{ title: 'Bar', done: true }]. When we post to the server a JSON string such as { title: 'Baz' } using POST /api/items interface, it stores this new item to the database. If we post to the server a JSON string such as { done: true } using PATCH /api/items/123 interface (123 is the id value of an item), it records this item as “done” on the database.

Having this settings, we can rewrite the todo_item_store.js of the previous example as follows:

todo_item_store.js

var TodoItemStore = Cape.createDataStoreClass({
  init: function() {
    this.items = [];
    this.refresh();
  },

  addItem: function(title) {
    var self = this;
    $.ajax({
      type: 'POST',
      url: '/api/items',
      data: { title: title }
    }).done(function(data) {
      self.refresh();
    });
  },

  toggle: function(item) {
    var self = this;
    $.ajax({
      type: 'PATCH',
      url: '/api/items/' + item.id,
      data: { done: !item.done }
    }).done(function(data) {
      self.refresh();
    });
  },

  refresh: function() {
    var self = this;
    $.ajax({
      type: 'GET',
      url: '/api/items.json'
    }).done(function(data) {
      self.items.length = 0;
      data.forEach(function(item) { self.items.push(item) });
      self.propagate();
    });
  }
});

$.ajax is a jQuery’s method to make an Ajax request. See http://api.jquery.com/jquery.ajax/ for details.

You can find a working demo using Ajax techniques on https://github.com/capejs/capejs-demo-on-rails. This demo is built as a Ruby on Rails application.

Note that the Cape.JS v1.2.0 introduced the CollectionAgent class which has a built-in functionality to send Ajax requests to the server.