ECMAScript 6 in action with the inheritance and the models
Last time, we saw a way to initialize an “ES6” project (http://k33g.github.io/2014/06/26/ES6-READY.html). Today, we’ll see how to play with the inheritance by creating models and collections.
Initially, we’ve to update our nodejs application to serve data. I need a database, so I’ll install NeDB (http://blog.mongodb.org/post/55693224724/nedb-a-lightweight-javascript-database-using-mongodbs).
Server side
This is your project structure:
es6-project/
├── node_modules/
├── public/
| ├── bower_components/
| ├── js/
| | └── app/
| | ├── models/
| | | ├── human.js
| | | └── humans.js
| | └── main.js
| └── index.html
├── .bowerrc
├── bower.json
├── package.json
└── app.js
Install NeDB
Update package.json
:
{
"name": "es6-project",
"description" : "es6-project",
"version": "0.0.0",
"dependencies": {
"express": "4.1.x",
"body-parser": "1.0.2",
"nedb": "0.10.5"
}
}
And to install NeDB: type npm install
REST API
Update app.js
:
var express = require('express')
, http = require('http')
, bodyParser = require('body-parser')
, DataStore = require('nedb')
, app = express()
, http_port = 3000
, humansDb = new DataStore({ filename: 'humansDb.nedb' });
app.use(express.static(__dirname + '/public'));
app.use(bodyParser());
// get all humans
app.get("/humans", function(req, res) {
humansDb.find({}, function (err, docs) {
res.send(docs);
});
});
// get a human by id
app.get("/humans/:id", function(req, res) {
humansDb.findOne({ _id: req.params.id }, function (err, doc) {
res.send(doc)
});
});
// delete human by id
app.delete("/humans/:id", function(req, res) {
humansDb.remove({ _id: req.params.id }, {}, function (err, numRemoved) {
res.statusCode = 200;
res.send({res:numRemoved});
});
});
// add a human
app.post("/humans", function(req, res) {
var human = req.body;
humansDb.insert(human, function (err, newDoc) {
res.statusCode = 301;
res.header("location", "/humans/"+newDoc._id).end();
});
});
// update a human
app.put("/humans/:id", function(req, res) {
humansDb.update({_id:req.params.id}, req.body, {}, function (err, numReplaced) {
res.statusCode = 200;
res.send({res:numReplaced});
})
});
// run app when database loaded
humansDb.loadDatabase(function (err) {
app.listen(http_port);
console.log("Listening on " + http_port);
});
Client side
Create the following directory: public/js/app/core
with to javascript files:
model.js
collection.js
Now, your project structure is like that:
es6-project/
├── node_modules/
├── public/
| ├── bower_components/
| ├── js/
| | └── app/
| | ├── core/
| | | ├── model.js
| | | └── collection.js
| | ├── models/
| | | ├── human.js
| | | └── humans.js
| | └── main.js
| └── index.html
├── .bowerrc
├── bower.json
├── package.json
└── app.js
Define our Model class
My model has fields (and url for REST calls):
var model = new Model({firstName:"John", lastName:"Doe"}, "/humans");
My model has persistence methods : save
(insert or update), fetch
, delete
:
model.save()
.done(() => /*victory!*/)
.fail(() => /*ouch!*/)
.always(() => /*that's all folks!*/);
My model has getters and setters:
model.get("firstName");
model.set("firstName", "John").set("lastName", "Doe");
My model has an id too and it’s set by the insert method of the server:
// add a human
app.post("/humans", function(req, res) {
var human = req.body;
humansDb.insert(human, function (err, newDoc) {
res.statusCode = 301;
res.header("location", "/humans/"+newDoc._id).end();
});
});
# Model implementation
class Model {
constructor (fields, url) {
this.url = url !== undefined ? url : "";
this.fields = fields !== undefined ? fields : {};
}
get (fieldName) {
return this.fields[fieldName];
}
set (fieldName, value) {
this.fields[fieldName] = value;
return this;
}
id() { return this.get("_id");}
save () {
return this.id() == undefined
? $.ajax({
url: this.url,
type: "POST",
data: this.fields,
success: (data) => { this.fields = data; }
})
: $.ajax({
url: `${this.url}/${this.id()}`,
type: "PUT",
data: this.fields,
success: (data) => { /*foo*/ }
});
}
fetch (id) {
return id == undefined
? $.ajax({
url: `${this.url}/${this.id()}`,
type: "GET",
data: this.fields,
success: (data) => { this.fields = data; }
})
: $.ajax({
url: `${this.url}/${id}`,
type: "GET",
data: this.fields,
success: (data) => { this.fields = data; }
});
}
delete (id) {
return id == undefined
? $.ajax({
url: `${this.url}/${this.id()}`,
type: "DELETE",
data: this.fields, success: (data) => { this.fields = data; }
})
: $.ajax({
url: `${this.url}/${id}`,
type: "DELETE",
data: this.fields,
success: (data) => { this.fields = data; }
});
}
}
export default Model;
Define our Collection class
My collection has:
- a property
model
(it’s a collection ofmodel
), - a property
models
(array of models), - and a property
url
for REST calls
var collection = new Collection(Model, "/humans", []);
My collection has methods:
add
to add modelssize
to get the length ofcollection.models
fetch
to get models from server
collection.fetch().done(() => {
console.log(collection.size())
});
# Collection implementation
class Collection {
constructor (model, url, models) {
this.model = model;
this.url = url !== undefined ? url : "";
this.models = models !== undefined ? models : [];
}
add (model) {
this.models.push(model);
return this;
}
size () { return this.models.length; }
fetch () {
return $.ajax({url: this.url, type: "GET", data: this.fields, success: (models) => {
this.models = []; /* empty list */
models.forEach((fields) => {
this.add(new this.model(fields));
})
}})
}
}
export default Collection;
Start with inheritance
It’s now it gets interesting :)
Define Human Model
Open public/js/app/models/human.js
and update it like that:
import Model from '../core/model';
class Human extends Model {
constructor (fields) {
//superclass's constructor invocation
super(fields, "/humans");
}
}
export default Human;
No surprise, the keyword for inheritance is extends
. And we’ve to import Model
before. Please note the use of super keyword. You’ve to invoke superclass’s constructor.
Define Humans Collection
Open public/js/app/models/humans.js
and update it like that:
import Collection from '../core/collection';
import Human from './human';
class Humans extends Collection{
constructor (humans) {
super(Human,"/humans",humans);
}
}
export default Humans;
And now,let’s play with Humans!
Open public/js/app/main.js
and update it like that:
import Human from './models/human';
import Humans from './models/humans';
class Application {
constructor () {
$("h1").html("E6 rocks!")
var humans = new Humans();
humans.fetch().done(()=>{ /* get all humans from database */
if(humans.size()==0) { /* no human in database, then populate the collection */
var bob = new Human({firstName:'Bob', lastName:'Morane'});
var john = new Human({firstName:'John', lastName:'Doe'});
var jane = new Human({firstName:'Jane', lastName:'Doe'});
/* save models */
bob.save().done(
() => john.save().done(
() => jane.save().done(
()=> humans.fetch().done(console.log(humans)) /* fetch again */
)
)
);
} else { /* display humans */
console.log(humans);
}
})
}
}
$(() => {
new Application();
});
You can now test it, run node app.js
and open http://localhost:3000/ with your browser. There is nothing to display except E6 rocks!, but if you open the console browser (developper tools), you can see the humans
collection with models:
Humans {model: function, url: "/humans", models: Array[0], add: function, size: function…}
model: function Human(fields) {}
url: "/humans"
models: Array[3]
0: Human
fields: Object
_id: "IHNGU1BY7Vv14ORk"
firstName: "John"
lastName: "Doe"
1: Human
fields: Object
_id: "WQpdowe3EAEbReuV"
firstName: "Jane"
lastName: "Doe"
2: Human
fields: Object
_id: "hFPDywuRsPdJM7Fa"
firstName: "Bob"
lastName: "Morane"
Display all Humans!
We’ll temporarily display the humans. And in a later tutorial we will create “ViewModels”.
This time, we’re going to use the “Observer Patern” with our collection and a “View” object. The collection is a “subject” (observable subject) and the view is an “observer”.
We’ll add an observers
property (an array of observers) ans two methods:
addObserver
(add an observer to observers)notifyObservers
(send message to all observers)
and we’ll update fetch
method to notify all observers when data are all fetched:
class Collection {
constructor (model, url, models) {
this.model = model;
this.url = url !== undefined ? url : "";
this.models = models !== undefined ? models : [];
this.observers = [];
}
addObserver (observer) {
this.observers.push(observer);
}
notifyObservers (context) {
this.observers.forEach((observer) => {
observer.update(context)
})
}
add (model) {
this.models.push(model);
return this;
}
size () { return this.models.length; }
fetch () {
return $.ajax({url: this.url, type: "GET", data: this.fields, success: (models) => {
models.forEach((fields) => {
this.add(new this.model(fields));
})
this.notifyObservers("fetch");
}})
}
}
export default Collection;
The View
Now, we can create a new view class HumansView
which is an observer and which will subscribe to humans collection notifications. Create a file humansView.js
in public/js/app/views
:
class HumansView { // this is an observer
constructor (humansCollection) {
humansCollection.addObserver(this);
this.collection = humansCollection;
this.list = $("humansList");
}
render () {
this.list.html(this.collection.models.reduce((previous, current) => {
return previous +
`<li><h3>
${current.id()} :
${current.get("firstName")}
${current.get("lastName")}
</h3></li>`;
}, "<ul>") + "</ul>");
}
update (context) {
console.log(context);
this.render();
}
}
export default HumansView;
Update index.html
:
- remove
<ul></ul>
tag - add
<humansList></humansList>
tag to<body>
Update public/js/app/main.js
like that:
import Human from './models/human';
import Humans from './models/humans';
import HumansView from './views/humansView';
class Application {
constructor () {
$("h1").html("E6 rocks!")
var humans = new Humans();
var humansView = new HumansView(humans);
humans.fetch().done(()=>{ /* get all humans from database */
if(humans.size()==0) { /* no human in database, then populate the collection */
var bob = new Human({firstName:'Bob', lastName:'Morane'});
var john = new Human({firstName:'John', lastName:'Doe'});
var jane = new Human({firstName:'Jane', lastName:'Doe'});
/* save models */
bob.save().done(
() => john.save().done(
() => jane.save().done(
()=> humans.fetch().done(console.log(humans)) /* fetch again */
)
)
);
} else { /* display humans */
console.log(humans);
}
})
}
}
$(() => {
new Application();
});
We’ve only:
- add reference to
HumansView
:import HumansView from './views/humansView';
- create instance of
HumansView
:var humansView = new HumansView(humans);
Now you can refresh http://localhost:3000/ and you’ll get a list of humans.
That’s all! I think it is easier with ECMAScript 6. Next time, we’ll see how to create better “ViewModels” object with Handlebars.
You can find source codes here : https://github.com/js-experiments/es6-project
Have a nice weekend!
Tweet