A walkthrough of the code for the PlastronJS TodoMVC example
Showing posts with label PlastronJS. Show all posts
Showing posts with label PlastronJS. Show all posts
Sunday, May 13, 2012
PlastronJS - the todomvc example walkthrough
A walkthrough of the code for the PlastronJS TodoMVC example
Dot vs square bracket notations in Closure
In javascript you can use square brackets and dot notation almost interchangeably:
Which is great and you can do things like:
so you can then put getters on an object (in this case A). e.g:
There are some other great uses as well such as the delegateEvents in backbone.js
But unfortunately in closure we can't do this because after compilation:
This is because string literals will not be compiled, but properties with dot notation will be renamed. Fear not however because we don't actually need to mix these two and it does help if you think about these not being the same.
If you have a look at plastronjs you'll notice that setting a schema will look like:
so the properties have strings and the get and set don't. Closure will rename strings but not properties. The reason they are like this is that property will be called on using model.set() and model.get() that take a string as the first argument and those can either be sent or received through a sync. So basically prop1 and prop2 can be EXTERNAL. get and set are only ever used inside our program so they are INTERNAL.
so how about the options object that gets passed to a model? it takes:
'attr', 'sync', 'schema' etc. Why are those in quotes? Well the fact is that I've allowed you to just pass through ordinary properties at the top level, so who is to say when those are compiled (sat attr -> a) that you haven't defined 'a' as a property? I'm allowing you to mix external and internals so to be safe I have to use the quotes. In doing that I then have to refer to the options object for object['attr'] instead of object.attr so we're losing a little compiled space but making things much easier to pass through attributes.
Just to make things easier I've also given you a special method in mvc.Model which will allow you to get around typing the string every time you need an attribute. What we can do is bind a function to that attribute to make things easier. Say we have an attribute 'star' that is either true or false. If I reuse it a lot, instead of doing model.get('star') I'd like something a bit easier. mvc.Model#getBinder to the rescue. Now you can do this:
The getBinder method is simple and looks like this:
It will allow you to get and set through a function whose name can be compiled and also makes it easy to call.
So what is the moral of the story? Well there are three:
Keep these in mind when you start and after a bit of practice you won't miss being able to mix notations, and it will certainly help you keep down differences between the compiled and uncompiled versions of your code.
a['property'] === a.property
Which is great and you can do things like:
function createGet(property, fn) {
A['get'+property] = fn(A[property]);
}
so you can then put getters on an object (in this case A). e.g:
A.one = 1;
A.two = 2;
var sayNumber = function(num) {return "number is: " + num;};
createGet('one', sayNumber);
createGet('two', sayNumber);
A.getOne(); // "number is : 1"
A.getTwo(); // "number is : 2"
There are some other great uses as well such as the delegateEvents in backbone.js
But unfortunately in closure we can't do this because after compilation:
a['property'] !== a.property
This is because string literals will not be compiled, but properties with dot notation will be renamed. Fear not however because we don't actually need to mix these two and it does help if you think about these not being the same.
If you have a look at plastronjs you'll notice that setting a schema will look like:
var schema = {
'prop1' : {
get: function ...
set: function ...
},
'prop2' : {
get: function ...
set: function ...
}
};
so the properties have strings and the get and set don't. Closure will rename strings but not properties. The reason they are like this is that property will be called on using model.set() and model.get() that take a string as the first argument and those can either be sent or received through a sync. So basically prop1 and prop2 can be EXTERNAL. get and set are only ever used inside our program so they are INTERNAL.
so how about the options object that gets passed to a model? it takes:
'attr', 'sync', 'schema' etc. Why are those in quotes? Well the fact is that I've allowed you to just pass through ordinary properties at the top level, so who is to say when those are compiled (sat attr -> a) that you haven't defined 'a' as a property? I'm allowing you to mix external and internals so to be safe I have to use the quotes. In doing that I then have to refer to the options object for object['attr'] instead of object.attr so we're losing a little compiled space but making things much easier to pass through attributes.
Just to make things easier I've also given you a special method in mvc.Model which will allow you to get around typing the string every time you need an attribute. What we can do is bind a function to that attribute to make things easier. Say we have an attribute 'star' that is either true or false. If I reuse it a lot, instead of doing model.get('star') I'd like something a bit easier. mvc.Model#getBinder to the rescue. Now you can do this:
var star = model.getBinder('star');
star(); // true
star(false);
star(); // false
The getBinder method is simple and looks like this:
mvc.Model.prototype.getBinder = function(key) {
return goog.bind(function(val) {
if (goog.isDef(val)) {
this.set(key, val);
} else {
return this.get(key);
}
}, this);
};
It will allow you to get and set through a function whose name can be compiled and also makes it easy to call.
So what is the moral of the story? Well there are three:
- If a property is visible externally or is mixed with external properties that use square brackets
- If a property is internal only to the project then use dot
- If you're using a lot of square brackets think about binding it to a function
Keep these in mind when you start and after a bit of practice you won't miss being able to mix notations, and it will certainly help you keep down differences between the compiled and uncompiled versions of your code.
Saturday, May 12, 2012
TodoMVC PlastronJS example
Here is a run through of how to get the todoMVC example, checkout the uncompiled code and how to compile changes you make.
If you're having trouble seeing the characters try clicking the youtube button on the video and watching it in fullscreen.
Tuesday, May 8, 2012
how to display a collection
There are two options when displaying a collection of controls, both with their drawbacks.
Refresh
The easiest one to master is the refresh. All you need to do is listen to any change to the collection then scrap all the child controls and re-render new ones. It's simple and means that your child controls won't need listeners on the models to change presentation - instead you can put logic in your templates to display the control with the model's current state.
This is the approach I've taken with the todomvc. It uses a lot less code (in fact compiled it is the smallest example on the entire site including the pure closure example - and it includes extra feats like routing!) and is pretty efficient for small lists. It can be slow though as it removes all the controls first and re-renders them and can cause memory leaks.
You should be cleaning up listeners on elements every time you remove a control. Luckily the closure library uses good.ui.component and automatically will cleanup listeners for you when you call dispose(). This wonderful mechanism has been used by mvc.Control so anytime you call a this.on, this.click, this.bind, etc. the handler is registered on the control and will be removed when you call mvc.Control#dispose().
Pretty neat eh? So PlastronJS has all the mechanisms you need to make this happen. It can cleanup for you on the refresh and can listen to any changes on a child that need you to refresh (anyModelChange).
Individual Updates
Now to the tricky part. What happens if you have complex child controls? well PlastronJS helps you with this as well. the collection has a listener for modelChange which only fires when the shape of the children change. Now what do I mean by shape?
The shape of the children refers to changes in sort order, or adding or removing children. If you want you can think of it as comparing an array of just the child model ids. So if you update a child but the list order doesn't change then modelChange will not fire. This means no re-rendering the entire list just for one small change.
But this comes at the price of complexity. The child models now have to manage their own display with listeners on their models. You will also need to write a function to run through the controls children and match the existing controls with it's collections child models, but what it means is less changes to the DOM and a faster site.
The truth is it can be complex managing this - (what happens if the child control disposes itself? how should you shuffle around the resorted nodes?) and as yet PlastronJS leaves you with the details.
In the next iteration though I'm planning some way of letting you attach what a child control should look like and have PlastronJS deal with the shape changes by moving controls in the least amount of steps. This will probably come as a new mvc.ListControl that inherits from mvc.Control so stay tuned!
Wednesday, April 18, 2012
PlastronJS By Example pt5
now we'll put in some more functionality which will include the count of todos the ability to check a todo as complete and also remove a todo.
first the list control we want to add in a count:
what I've done is add in a div at the end and then put in a listener on modelChange which will fire if any models are added or removed (or are changed so that they are sorted differently) and then just display the number of notes. Now on to the todo:
Here I've added in a checkbox and a delete div and given them click functions. On clicking the checkbox it looks to see if the element is checked and changes the style of the text. It also sets 'complete' on the model or unsets it.
the delete will call dispose on the model which in turn will let the collection know it is disposed so the listener we put on to the collection will fire and update the count. We then dispose our control which will remove itself from the dom and clean up any event listeners it has setup.
first the list control we want to add in a count:
goog.provide('todomvc.listcontrol');
goog.require('mvc.Control');
goog.require('todomvc.todocontrol');
todomvc.listcontrol = function(model) {
goog.base(this, model);
};
goog.inherits(todomvc.listcontrol, mvc.Control);
todomvc.listcontrol.prototype.createDom = function() {
this.el = goog.dom.htmlToDocumentFragment("<div>" +
"<div>Todo</div>" +
"<div><input type='text' class='todoform'/></div>" +
"<div class='todolist'></div>" +
"<div class='count'></div></div>");
console.log(this.el);
this.setElementInternal(this.el);
};
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);
this.addChild(newModelControl);
newModelControl.render(this.getEls('.todolist')[0]);
}, 'todoform');
this.getModel().modelChange(function() {
goog.dom.setTextContent(this.getEls('.count')[0],
this.getModel().getLength() + ' notes');
}, this);
};
what I've done is add in a div at the end and then put in a listener on modelChange which will fire if any models are added or removed (or are changed so that they are sorted differently) and then just display the number of notes. 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("<div>" +
"<input type='checkbox' class='complete'/>" +
"<div class='todoedit'></div>" +
"<div class='delete'>X</div>" +
"</div>");
console.log(this.el);
this.setElementInternal(this.el);
};
todomvc.todocontrol.prototype.makeEditable = function() {
this.getEls('.todoedit')[0].innerHTML = "<input type='text' value='"+this.getModel().get('text')+"'/>";
this.editable = true;
};
todomvc.todocontrol.prototype.makeUneditable = function() {
this.getEls('.todoedit')[0].innerHTML = this.getModel().get('text');
this.editable = false;
};
todomvc.todocontrol.prototype.enterDocument = function() {
this.makeUneditable();
this.on('keyup', function(e) {
this.getModel().set('text', this.getEls('input')[1].value);
});
this.on('focusout', function() {
this.makeUneditable();
});
this.click(function() {
if(!this.editable)
this.makeEditable();
}, "todoedit");
this.click(function(e) {
if(e.target.checked) {
this.getEls(".todoedit")[0].style.textDecoration = 'line-through';
this.getModel().set('complete', true);
} else {
this.getEls(".todoedit")[0].style.textDecoration = 'none';
this.getModel().unset('complete');
}
}, "complete");
this.click(function(e) {
this.getModel().dispose();
this.dispose();
}, "delete")
};
Here I've added in a checkbox and a delete div and given them click functions. On clicking the checkbox it looks to see if the element is checked and changes the style of the text. It also sets 'complete' on the model or unsets it.
the delete will call dispose on the model which in turn will let the collection know it is disposed so the listener we put on to the collection will fire and update the count. We then dispose our control which will remove itself from the dom and clean up any event listeners it has setup.
Monday, April 16, 2012
PlastronJS by example pt4
today we're going to display the todo and allow some editing.
First I'll create the control for a todo item:
It should look fairly similar to our list control. It has a createDom method where I'm putting in the div and an enterDocument where I setup all the listeners. I've also created makeEditable and makeUneditable functions which will put in an input which will listen to key up events to change the models text.
I've also put in a focusout event (notice it's not blur because blur events don't bubble so won't reach the control's listener) to go back to uneditable mode. Now we need to add this to our list control:
first thing I did was add in the goog.require at the top. Since this is a new file we'll have to add it to out deps.js with the command we used in the first post:
lib/closure-library/closure/bin/calcdeps.py --dep lib/closure-library --input js/main.js --path lib/plastronjs --path lib/plastronjs/sync --path js/ --output_mode deps > deps.js
I also added in the goog.base() for enterDocument. This is because I'm adding the controls as a child and goog.ui.Component does some things with it's enterDocument to setup relationships between itself and child components.
I also changed the form to just listen to a keyup as this is what is being done on todomvc's website (and debugging forms can be a pain as the browser will submit the form even if you break in the javascript).
and then the part at the bottom I setup the control, pass it the new model (mvc.Collection#newModel returns the model that was created and added) added it as a child of the control and then rendered it in to a div that I put in to hold the children.
save those changes and run then. You should be able to see that you can create new notes, click them and edit them. The only thing we use to display the test is a call to get('text') so we can see that the changes are being saved to the model.
First I'll create the control for a todo item:
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("<div>" +
"<div class='todoedit'></div>" +
"</div>");
console.log(this.el);
this.setElementInternal(this.el);
};
todomvc.todocontrol.prototype.makeEditable = function() {
this.getEls('.todoedit')[0].innerHTML =
"<input type='text' value='" + this.getModel().get('text') + "'/>";
this.editable = true;
};
todomvc.todocontrol.prototype.makeUneditable = function() {
this.getEls('.todoedit')[0].innerHTML = this.getModel().get('text');
this.editable = false;
};
todomvc.todocontrol.prototype.enterDocument = function() {
this.makeUneditable();
this.on('keyup', function(e) {
this.getModel().set('text', this.getEls('input')[1].value);
});
this.on('focusout', function() {
this.makeUneditable();
});
this.click(function() {
if(!this.editable)
this.makeEditable();
});
};
It should look fairly similar to our list control. It has a createDom method where I'm putting in the div and an enterDocument where I setup all the listeners. I've also created makeEditable and makeUneditable functions which will put in an input which will listen to key up events to change the models text.
I've also put in a focusout event (notice it's not blur because blur events don't bubble so won't reach the control's listener) to go back to uneditable mode. Now we need to add this to our list control:
goog.provide('todomvc.listcontrol');
goog.require('mvc.Control');
goog.require('todomvc.todocontrol');
todomvc.listcontrol = function(model) {
goog.base(this, model);
};
goog.inherits(todomvc.listcontrol, mvc.Control);
todomvc.listcontrol.prototype.createDom = function() {
this.el = goog.dom.htmlToDocumentFragment("<div>" +
"<div>Todo</div>" +
"<div><input type='text' class='todoform'/></div>" +
"<div class='todolist'></div></div>");
console.log(this.el);
this.setElementInternal(this.el);
};
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);
this.addChild(newModelControl);
newModelControl.render(this.getEls('.todolist')[0]);
}, 'todoform');
};
first thing I did was add in the goog.require at the top. Since this is a new file we'll have to add it to out deps.js with the command we used in the first post:
lib/closure-library/closure/bin/calcdeps.py --dep lib/closure-library --input js/main.js --path lib/plastronjs --path lib/plastronjs/sync --path js/ --output_mode deps > deps.js
I also added in the goog.base() for enterDocument. This is because I'm adding the controls as a child and goog.ui.Component does some things with it's enterDocument to setup relationships between itself and child components.
I also changed the form to just listen to a keyup as this is what is being done on todomvc's website (and debugging forms can be a pain as the browser will submit the form even if you break in the javascript).
and then the part at the bottom I setup the control, pass it the new model (mvc.Collection#newModel returns the model that was created and added) added it as a child of the control and then rendered it in to a div that I put in to hold the children.
save those changes and run then. You should be able to see that you can create new notes, click them and edit them. The only thing we use to display the test is a call to get('text') so we can see that the changes are being saved to the model.
Thursday, April 12, 2012
PlastronJS by example pt3
Now we're going to hook up the form we built to crete new todo models.
First thing we're going to do is modify our main.js to make it easier to access the todolist in our console (plus fix up an error from last post).
The only real change to the above is that I've added window['a']= todolist at the end. This will be removed later, but I like having easy access to specific variables in the console when I'm developing. This will allow me to call the todolist with just a.method.
Next up is the Control:
What this has done is add in the enterDocument function. Usually we should call goog.base() on this as well as it inherits from goog.ui.component and does some things we want to keep (like figuring out that it is attached to a document so child components will render). We put the setup in here because this is when we are first guaranteed that the DOM structure we create in createDom is in the document
From here I've call .on which is a method that we use for event handling. I've passed in the event to listen for and a function to handle it. There are other optional parameters I could pass such as a classname to only target one form and then a handler, but the function is automagically bound to the control and there is only one form so I don't need to.
The function stops the event and returns false to prevent the default submit, then it grabs the text from the first input under our control. I'm then getting the controls model and using the collection's newModel method which will put a new mvc.Model at the top and passing in the text and that's it.
try opening up the html file, typing something in to the field and pressing enter. You can then open up your javascript console and see the new model by entering in:
Next post we'll create a new control for todos and have them display
First thing we're going to do is modify our main.js to make it easier to access the todolist in our console (plus fix up an error from last post).
goog.provide('todomvc.main');
goog.require('mvc.Collection');
goog.require('todomvc.listcontrol');
todomvc.main = function() {
var todolist = new mvc.Collection();
var todolistControl = new todomvc.listcontrol(todolist);
todolistControl.createDom();
todolistControl.render(document.body);
window['a'] = todolist;
};
The only real change to the above is that I've added window['a']= todolist at the end. This will be removed later, but I like having easy access to specific variables in the console when I'm developing. This will allow me to call the todolist with just a.method.
Next up is the Control:
Whatgoog.provide('todomvc.listcontrol');
goog.require('mvc.Control');
todomvc.listcontrol = function(model) {
goog.base(this, model);
};
goog.inherits(todomvc.listcontrol, mvc.Control);
todomvc.listcontrol.prototype.createDom = function() {
this.el = goog.dom.htmlToDocumentFragment("<div>" +
"<div>Todo</div>" +
"<div><form><input type='text' /></form></div>" +
"</div>");
console.log(this.el);
this.setElementInternal(this.el);
};
todomvc.listcontrol.prototype.enterDocument = function() {
this.on('submit', function(e) {
e.preventDefault();
e.stopPropagation();
var text = (this.getEls('input')[0]).value;
this.getModel().newModel({'text': text});
return false;
});
};
What this has done is add in the enterDocument function. Usually we should call goog.base() on this as well as it inherits from goog.ui.component and does some things we want to keep (like figuring out that it is attached to a document so child components will render). We put the setup in here because this is when we are first guaranteed that the DOM structure we create in createDom is in the document
From here I've call .on which is a method that we use for event handling. I've passed in the event to listen for and a function to handle it. There are other optional parameters I could pass such as a classname to only target one form and then a handler, but the function is automagically bound to the control and there is only one form so I don't need to.
The function stops the event and returns false to prevent the default submit, then it grabs the text from the first input under our control. I'm then getting the controls model and using the collection's newModel method which will put a new mvc.Model at the top and passing in the text and that's it.
try opening up the html file, typing something in to the field and pressing enter. You can then open up your javascript console and see the new model by entering in:
a.at(0); // returns your new model
a.at(0).get('text'); // returns the text in your new model which should be what you typed in
Next post we'll create a new control for todos and have them display
Tuesday, April 10, 2012
PlastronJS By example pt2
what we're going to do is setup a new todo list and put in the start of the display.
So first we'll change the HTML to just call the main function with:
<script>
todomvc.main();
</script>
Next we'll create a new file under our js folder and call it list control.js - this will be the C in our MVC for the todo list. The contents of our file should look like this:
The first line is for dependancies and tells calcdeps what class the file provides. We also need to require mvc.Control because we'll be inheriting from it.
next comes the constructor function which calls good.base(this) which tells it to call the constructor function of the class it inherits from.
goog.inherits sets up the inheritance chain.
The createDom is from good.ui.Component and is what is used to setup the DOM for the control. For now I've just put in the template as a string and used the good.dom.htmlToDocumentFragment to create the elements and then set the top element as the controls internal element.
Now we need to change min.'s to use this control:
We need to require our list control and pass it a model (in this case our todo list collection). We then create the Dom and finally render it in to the document.
That should be enough to get us the title and the input box. Next we'll setup the input box to create todo models.
So first we'll change the HTML to just call the main function with:
<script>
todomvc.main();
</script>
Next we'll create a new file under our js folder and call it list control.js - this will be the C in our MVC for the todo list. The contents of our file should look like this:
goog.provide('todomvc.listcontrol');
goog.require('goog.dom');
goog.require('mvc.Control');
todomvc.listcontrol = function(model) {
goog.base(this, model);
};
goog.inherits(todomvc.listcontrol, mvc.Control);
todomvc.listcontrol.prototype.createDom = function() {
this.el = goog.dom.htmlToDocumentFragment("<div>" +
"<div>Todo</div>" +
"<div><form><input type='text' /></form></div>" +
"</div>");
console.log(this.el);
this.setElementInternal(this.el);
};
The first line is for dependancies and tells calcdeps what class the file provides. We also need to require mvc.Control because we'll be inheriting from it.
next comes the constructor function which calls good.base(this) which tells it to call the constructor function of the class it inherits from.
goog.inherits sets up the inheritance chain.
The createDom is from good.ui.Component and is what is used to setup the DOM for the control. For now I've just put in the template as a string and used the good.dom.htmlToDocumentFragment to create the elements and then set the top element as the controls internal element.
Now we need to change min.'s to use this control:
goog.provide('todomvc.main');
goog.require('mvc.Collection');
goog.require('todomvc.listcontrol');
todomvc.main = function() {
var todolist = new mvc.Collection();
var todolistControl = new todomvc.listcontrol(this.todolist);
todolistControl.createDom();
todolistControl.render(document.body);
};
We need to require our list control and pass it a model (in this case our todo list collection). We then create the Dom and finally render it in to the document.
That should be enough to get us the title and the input box. Next we'll setup the input box to create todo models.
Monday, April 9, 2012
PlastronJS by example pt.1
We're going to use PlastronJS to make a todo app that could go on TodoMVC
Setup
first thing we need to setup a directory where we can put the application and the library.
I've created a folder called todomvc with two folders underneath it, lib and js. First thing I need to do is put in the closure library. You can get the closure library from here: http://code.google.com/p/closure-library/downloads/list
Next I need to put in PlastronJS which I can get from here: https://github.com/rhysbrettbowen/PlastronJS
Put those two under the lib folder.
Now under the js folder create a file called min.'s and insert this code:
goog.provide('todomvc.main');
goog.require('mvc.Model');
todomvc.main = function() {
this.a = mvc.Model.create();
};
All that it's doing is providing the todo.mvc function, requiring our mvc.Model and giving us a function to create a model.
Now we need some HTML to display, pop this in main.html which you can create under the root:
<!doctype html>
<html>
<head>
<title>Example: TodoMVC</title>
</head>
<body>
<div id="hello"></div>
<script src="lib/closure-library/closure/goog/base.js"></script>
<script src="deps.js"></script>
<script src="js/main.js"></script>
</body>
</html>
last we need to create the deps.js which is a dependency map of the js files we need to use. Head over to your command line and put in:
lib/closure-library/closure/bin/calcdeps.py --dep lib/closure-library --input js/main.js --path lib/plastronjs --path lib/plastronjs/sync --path js/ --output_mode deps > deps.js
Now we need some HTML to display, pop this in main.html which you can create under the root:
<!doctype html>
<html>
<head>
<title>Example: TodoMVC</title>
</head>
<body>
<div id="hello"></div>
<script src="lib/closure-library/closure/goog/base.js"></script>
<script src="deps.js"></script>
<script src="js/main.js"></script>
</body>
</html>
last we need to create the deps.js which is a dependency map of the js files we need to use. Head over to your command line and put in:
lib/closure-library/closure/bin/calcdeps.py --dep lib/closure-library --input js/main.js --path lib/plastronjs --path lib/plastronjs/sync --path js/ --output_mode deps > deps.js
Run it
That should be all you need to start. You can open the page in your browser and you won't see much. Open up your javascript console and put in
You can then play around with the model through a.a
Next post we'll got through creating the models for the todo items and how to use them with sync
var a = new todomvc.main();
You can then play around with the model through a.a
a.a.set('a',1);
a.a.get('a') // 1
b = a.getBinder('a');
b(); // 1
b(2);
a.a.get('a') // 2
Next post we'll got through creating the models for the todo items and how to use them with sync
Friday, April 6, 2012
PlastronJS
For an MVC I'm going to be using PlastronJS.
It's a shameful plug because I wrote it. It should be familiar if you've used anything like backbone, spine, angular, ember or any number of MVC frameworks out there. So why did I write another one? A few reasons.
You'll also notice that the functions are bound to the model, so you can just call 'this'.
There is also compare functionality in the schema. A model decides what has changed by comparing the current attribute and a saved attribute from the last change. When saving an attribute it does an unsafe recursive clone (so no recursive references in the model's data please) so the object will not be equal. Primitive types and arrays of primitive types are checked for equality, otherwise you'll probably want to define a comparison function.
There are already a couple of functions under mvc.Model.Compare you can use. RECURSIVE goes through the object/array recursively and checks that everything matches. There is also a STRING and a SERIALZE that compares the .toSring() and JSON.serialze() of the object against each other. You may notice I didn't need to give a cmp for 'star' and that's because event though it is under an object it's getter will only return a primitive type.
If you want to learn more about Schemas in PlastronJS head over to https://github.com/rhysbrettbowen/PlastronJS and go through the README and the source code (which should be commented thoroughly). Hopefully you'll be able to define an interface for your complex models which will relieve a lot of the stress when working with them in more complex applications.
It's a shameful plug because I wrote it. It should be familiar if you've used anything like backbone, spine, angular, ember or any number of MVC frameworks out there. So why did I write another one? A few reasons.
- There isn't a framework that integrates well with the closure compiler
- Most frameworks have dependancies on jquery and not closure-library
- The frameworks are fairly lightweight
- I was bored
PlastronJS is slightly different to what you've been used to. At it's core it works the same way - there are models that communicate to a data layer through sync and you can bind functions to changes in the models data.
So what is different? Probably the best reason to use PlastronJS are the schemas. A schema can be put on a model to provide you with a different interface for accessing the data. For instance at Catch we hold information about notes under their 'annotations'. If I want to star a note I would have to get the annotations object, change the 'catch:starred' attribute and then set the 'catch:starred' attribute. I'd also have to listen to changes on the annotations object, so any other changes to that would fire the function.
SCHEMA
A schema allows the setting and getting of custom attributes and helps you describe them based on attributes that already exist in the model. For instance I can create a star attribute like this:
// schema object
var schema = {
'star': {
get: function(annotations) {
return annotations && annotations['catch:starred'];
},
set: function(star) {
var ann = this.get('annotations');
goog.object.extend(ann, {'catch:starred': star});
this.set('annotations', ann, true);
},
require: ['annotations']
}
};
The require attribute tells us what attributes from the model we need to get/set star. The get function will receive the get() value of our requires array. So if we required two elements we would have those two as inputs to the get function. The get function can also throw errors for validation and they will be caught by the model's error function.
When setting the value we'll usually pass the optional "silent" parameter if using the set() with other attributes. This is because the original set() call for star is the one we want to decide whether to fire a change event. You could also just set it on the actually data in the model like this:
When setting the value we'll usually pass the optional "silent" parameter if using the set() with other attributes. This is because the original set() call for star is the one we want to decide whether to fire a change event. You could also just set it on the actually data in the model like this:
this.attr_['annotations'] = ann;
You'll also notice that the functions are bound to the model, so you can just call 'this'.
There is also compare functionality in the schema. A model decides what has changed by comparing the current attribute and a saved attribute from the last change. When saving an attribute it does an unsafe recursive clone (so no recursive references in the model's data please) so the object will not be equal. Primitive types and arrays of primitive types are checked for equality, otherwise you'll probably want to define a comparison function.
// schema object
var schema = {
'annotations': {
cmp: mvc.Model.Compare.RECURSIVE
}
};
There are already a couple of functions under mvc.Model.Compare you can use. RECURSIVE goes through the object/array recursively and checks that everything matches. There is also a STRING and a SERIALZE that compares the .toSring() and JSON.serialze() of the object against each other. You may notice I didn't need to give a cmp for 'star' and that's because event though it is under an object it's getter will only return a primitive type.
If you want to learn more about Schemas in PlastronJS head over to https://github.com/rhysbrettbowen/PlastronJS and go through the README and the source code (which should be commented thoroughly). Hopefully you'll be able to define an interface for your complex models which will relieve a lot of the stress when working with them in more complex applications.
Subscribe to:
Posts (Atom)