Collection agent (1) - Cape.JS Primer
On the previous lecture, I upgraded Cape.JS 1.1 to 1.2 of “Todo list application”.
From this lecture, I’ll rewrite the source code by using the new class CollectionAgent
introduced on Cape.JS 1.2. The behavior of the application doesn’t change but I’ll show you the amount of source code decrease a lot.
First, rewrite app/assets/javascripts/application.js
as following.
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require capejs
//= require bootstrap
//= require lodash
//= require es6-promise
//= require fetch
//= require_tree .
//= require_self
Cape.defaultAgentAdapter = 'rails';
I modified 4 points.
- I added the directive
//= require es6-promise
. - I added the directive
//= require fetch
. - I added the directive
//= require_self
. - I added the JavaScript code
Cape.defaultAgentAdapter = 'rails';
.
The first and second one make the browser except Chrome and Firefox hold the function of Fetch API. The directive //= require_self
is necessary to write the JavaScript code in the file application.js
.
The last sentence sets the default adapter of CollectionAgent
. “Adapter” means here the JavaScript library adjusting the connection to API server.
If you want to connect API server implemented on Ruby on Rails like our “Todo list”, you need to set the default adapter before making the instance CollectionAgent
.
By this setting, the appropriate value is set to the header of X-CSRF-Token
of HTTP request sent to API server. Unless you set like this, the request by the method except GET/HEAD is rejected.
'rails'
. If you want to use API server implemented by the one except Rails, you need to make the adapter by yourself.
Next, change the structure of JSON data that API server returns as “task’s list”.
So far, app/views/api/tasks/index.jbuilder
is wrote like following.
json.array! @tasks, :id, :title, :done
The JSON data created from this code is like following.
[
{ "id": 1, "title": "Buy cat food", "done": true },
{ "id": 2, "title": "Take away the trash", "done": false }
]
But, the collection agent requires JSON data of the structure like following,
{
"tasks": [
{ "id": 1, "title": "Buy cat food", "done": true },
{ "id": 2, "title": "Take away the trash", "done": false }
]
}
The whole JSON data is necessary to be the object not the array. And, there is the key which is conform to “the resource name” of the collection agent in the object, and the key must the array of the value. For this example, "tasks"
is the resource name. (For more information, I’ll explain later.)
Then, rewrite app/views/api/tasks/index.jbuilder
as following.
json.tasks do
json.array! @tasks, :id, :title, :done
end
Next, define the class of the collection agent. The name of the class is TaskCollectionAgent
. You can decide the name as you want not like the model name of Rail.
Open a new file task_collection_agent.es6
on the directory app/assets/javascripts
as following.
class TaskCollectionAgent extends Cape.CollectionAgent {
constructor(client, options) {
super(client, options);
this.basePath = '/api/';
this.resourceName = 'tasks';
}
}
The class of the collection agent inherits the class Cape.CollectionAgent
. On the constructor, it sets some properties. The property basePath
is string based on URL of Ajax request. The default value is '/'
. On our “Todo list” application, we set like that because it accesses to the path under the directory /api/
.
basePath
corresponds to namespace.
The property resourceName
means “resource name” wrote above. This value connects to the value of the property basePath
when the collect agent creates URL of Ajax request. Also, it’s used to acquire the array from JSON data that backs from API server as the key.
The class TaskCollectionAgent
just write the constructor but is able to acquire the task’s list from the server already.
Open app/assets/javascripts/todo_list.es6
on the text editor. Existing the method init()
is wrote like following.
init() {
this.ds = new TaskStore();
this.ds.attach(this);
this.editingTask = null;
this.ds.refresh();
}
Rewrite it as following.
init() {
this.agent = new TaskCollectionAgent(this);
this.editingTask = null;
this.agent.refresh();
}
Collection agent doesn’t have the method attach()
not like the data store. Instead of it, it assigns the component that is the collection agent’s “client” as constructor’s first parameter.
In addition, developers must implement the method refresh()
in the case of the data store, collection agent have existing refresh()
.
Look at app/assets/javascripts/task_store.es6
. The method refresh()
is wrote like following.
refresh() {
$.ajax({
type: 'GET',
url: '/api/tasks'
}).done(data => {
this.tasks = data;
this.propagate();
});
}
The instance method refresh()
of the class TaskCollectionAgent
corresponds approximately to it. But, the task’s array stores in the property objects
not the property tasks
.
Keep rewriting app/assets/javascripts/todo_list.es6
. Next is the method render()
. Existing code is following.:
render(m) {
m.ul(m => {
this.ds.tasks.forEach(task => {
m.li(m => this.renderTask(m, task));
});
});
if (this.editingTask) this.renderUpdateForm(m);
else this.renderCreateForm(m);
}
Rewrite it as following.
render(m) {
m.ul(m => {
this.agent.objects.forEach(task => {
m.li(m => this.renderTask(m, task));
});
});
// if (this.editingTask) this.renderUpdateForm(m);
// else this.renderCreateForm(m);
}
I changed 3 points. Rewrite this.ds.tasks.forEach
to this.agent.objects.forEach
on the third line. And, comment out once the seventh and eighth lines. (to avoid errors)
The task’s list will display by changes above.
The function to toggle the flag “done” of the task and the function to delete tasks don’t move yet. On the next lecture, I’ll modify a part related these functions.