Wednesday, April 25, 2012

PlastronJS by example pt6

this post we're going to move code in to the todomvc template.

First download the todomvc template from https://github.com/addyosmani/todomvc (it should be in a folder called template) and copy everything across to the folder. Next we'll move the contents of /lib to /js/libs.

now some editing. I edited the index.html file to add a class name the same as id to "toggle-all" as I want to use the mvc.Control to handle those events and at the moment it only takes a classname to clarify the element, and one to the main input as "todo-entry" for the same reason.

Next we edit the part at the bottom where it asks for our scripts so we have this:


<script src="js/libs/closure-library/closure/goog/base.js"></script>
  <script src="deps.js"></script>
<script src="js/app.js"></script>
<script>
todomvc.main();
</script>

I then copy and pasted our main.js in to app.'s and because it's in a closer I gave the window a reference to todomvc:

/*

[MIT licensed](http://en.wikipedia.org/wiki/MIT_License)
(c) [You](http://addyosmani.github.com/todomvc/)

*/
goog.provide('todomvc.main');

goog.require('mvc.Collection');
goog.require('todomvc.listcontrol');
goog.require('goog.dom');


(function() {

// Your starting point. Enjoy the ride!

todomvc.main = function() {
  var todolist = new mvc.Collection();
  var todolistControl = new todomvc.listcontrol(todolist);
  todolistControl.decorate(goog.dom.getElement('todoapp'));
  window['a'] = todolist;
  window['b'] = todolistControl;
};

window.todomvc = todomvc;

})();

you might also notice that above I changed the todolistcontrol to decorate rather than render. You do this when there is already a DOM structure that the control should use. And here is the list control:

goog.provide('todomvc.listcontrol');

goog.require('mvc.Control');
goog.require('todomvc.todocontrol');
goog.require('goog.dom');

todomvc.listcontrol = function(model) {
  goog.base(this, model);
  this.getModel().setSchema({
    'completed': {
      get: function() {
        return this.getModels(function(mod) {
          return mod.get('complete');
        });
      },
      models: true
    }
  });
};
goog.inherits(todomvc.listcontrol, mvc.Control);



todomvc.listcontrol.prototype.enterDocument = function() {
  goog.base(this, 'enterDocument');

  this.on('keyup', function(e) {
    // on return
    if (e.keyCode != 13) return;

    // create new model
    var text = (this.getEls('input')[0]).value;
    var newModel = this.getModel().newModel({'text': text});

    //create new model control
    var newModelControl = new todomvc.todocontrol(newModel);
    newModelControl.render(goog.dom.getElement('todo-list'));
  }, 'todo-entry');

  this.click(function(e) {
    goog.array.forEach(this.getModel().get('completed'), function(model) {
      model.dispose();
    });
  }, 'clear-completed');

  this.click(function(e) {
    goog.array.forEach(this.getModel().getModels(), function(mod) {
      mod.set('complete', e.target.checked);
    });
  }, 'toggle-all');

  this.getModel().modelChange(function() {
    goog.dom.getElement('main').style.display = this.getModels().length ?
        'block' : 'none';
    goog.dom.getElementsByTagNameAndClass('footer')[0].style.display =
        this.getModels().length ? 'block' : 'none';
  });

  goog.dom.getElement('main').style.display =
      this.getModel().getModels().length ? 'block' : 'none';
  goog.dom.getElementsByTagNameAndClass('footer')[0].style.display =
      this.getModel().getModels().length ? 'block' : 'none';

  this.getModel().bind('completed', function(mods) {
    goog.dom.setTextContent(goog.dom.getElement('todo-count'),
        (this.getModel().getModels().length - mods.length) + ' items left');
  }, this);
};

What I've done is add a schema to the list. The schema lists a new attribute on the list called completed. It has a get which tells me what I want back and model: true which tells the schema that the get uses the list of models and so changes should fire when the models change. I can then get a list of completed models any time by using .get('completed') but more importantly I can listen for changes on 'completed'.

We no longer need the createDom as it's already there for us so we move on to enterDocument.

The keyup event doesn't change except to tell the new models where they should render.

The next two clicks are for the check all and clear completed. They both run through the models in the list and one will set their complete value the other will tell the model to dispose itself.

The next is on a model change and will hide the footer and list if need be, I have similar lines of code to make it run the first time (in case in the future I want to already have notes displayed).

The last one binds to the 'completed' key I set in the schema and will update the items left when the completed number changes.

Now on to the todo:

goog.provide('todomvc.todocontrol');

goog.require('mvc.Control');

todomvc.todocontrol = function(model) {
  goog.base(this, model);
  this.editable = false;
};
goog.inherits(todomvc.todocontrol, mvc.Control);



todomvc.todocontrol.prototype.createDom = function() {
  this.el = goog.dom.htmlToDocumentFragment('<li>' +
          '<div class="view">' +
            '<input class="toggle" type="checkbox">' +
            '<label>' + this.getModel().get('text') + '</label>' +
            '<a class="destroy"></a>' +
          '</div>' +
          '<input class="edit" type="text" value="Create a TodoMVC template">' +
        '</li>');
 this.setElementInternal(this.el);
};


todomvc.todocontrol.prototype.enterDocument = function() {
  this.click(function(e) {
    if (e.target.checked) {
      this.getModel().set('complete', true);
    } else {
      this.getModel().set('complete', false);
    }
  }, 'toggle');

  this.getModel().bind('complete', function(complete) {
    if (complete) {
      goog.dom.classes.add(this.getElement(), 'done');
    } else {
      goog.dom.classes.remove(this.getElement(), 'done');
    }
    this.getEls('.toggle')[0].checked = complete;
  }, this);

  this.getModel().bindUnload(function() {
    this.dispose();
  }, this);

  this.click(function(e) {
    e.preventDefault();
    this.getModel().dispose();
    return false;
  }, "destroy")
};

here I change the createDom to use the HTML in the index.html (and I remove it from the index.html). I have four listeners setup, two are listening on the view and two on the model. So I listen on the view for the check box or the dispose button and I set the complete attribute or dispose of the model. I also listen to the complete attribute and set the views class and toggle check and lastly I listen for dispose on the model and dispose of my control.

Last thing to do is run the depswriter with the new file and directories:


js/libs/closure-library/closure/bin/calcdeps.py --dep js/libs/closure-library --input js/app.js --path js/libs/plastronjs --path js/libs/plastronjs/sync --path js/ --output_mode deps > deps.js

and that's it!

I have removed the functionality to edit a note but that shouldn't be too tricky. Next thing we need to do is put in comments for the functions and see how it all compiles.

N.B: download latest PlastronJS - there are a few fixes



3 comments:

  1. You write that you added class "todomvc-entry" to main input, but in code you expect "todo-entry" class. It took me a while to figure out, but it made me read the code.

    ReplyDelete
  2. thanks for that - I've updated the article

    ReplyDelete