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 of model),
  • 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 models
  • size to get the length of collection.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 humanscollection 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!

blog comments powered by Disqus

Related posts