MooVeeStar

A client-side MV* Framework built ontop of MooTools

by Regis Gaughan, III

MooVeeStar is a client-side MV* Framework built ontop of MooTools. It has been built to feel familiar while harnessing the power and practices of the MooTools library.

Introduction

The advancement of JavaScript the past decade has been nothing short of breathtaking. No longer is it a simple tool to popup an image slideshow or validate a contact form. It is a powerful tool that can be used to drive massive single-page websites. And if you've ever created a large-scale application you know all to well how you can quickly loose control of your client-side codebase in a jumbled mess of JavaScript.

There are plenty of great JavaScript libraries out there, and no shortage of frameworks to help you structure your code better. Unfortunately, most were built for specific libraries that don't lend themselves well to structured code in the first place, or were built to be completey fairly library agnostic. These simply add unnecessary bloat recreating what MooTools already has while simultaneously leaving out powerful features of it. So you've been writing structured code with MooTools already, now MooVeeStar is here to further optimize your development and put your app in the spotlight.

Documentation

MooVeeStar

MooVeeStar is the object/namespace that holds the MooVeeStar modules. However, the MooVeeStar object itself is an instantiated MooTools Events object, and can therefore be used as a global mediator between components:

MooVeeStar.addEvent('someEvent', function(eventObject){ alert(eventObject.say); }); MooVeeStar.fireEvent('someEvent', { say:'hi'});
MooVeeStar.Model
The MooVeeStar.Model is a MooTools class that implements the Events class already.
idProperty model.idProperty
A configurable key that will be used to uniquely identify the model. It will default to id.
properties model.properties

A configurable object with a properties hash where you can: Set an initial value; Override the default get and/or set methods; Define validate and/or sanitize methods for each property.

var Employee = new Class({ Extends: MooVeeStar.Model, idProperty: 'ssn', properties: { employed: { initial: true }, fullname: { set: function(value){ var names = (value || '').split(' '); this.set('firstname', names[0] || null); this.set('lastname', names[1] || null); }, get: function(){ return this.get('firstname')+' '+this.get('lastname'); } }, ssn: { validate: function(value){ return /^\d{3}-\d{2}-\d{4}$/.test(value) || 'The SSN was not formatted correctly'; } } } }); var employee = new Employee({'ssn':'123-45-6789', 'firstname':'John','lastname':'Smith'}); employee.addEvent('change:ssn', function(event){ alert(employee.get('fullname') + "'s SSN has changed."); }); employee.addEvent('error:ssn', function(event){ alert(event.error); }); // If the ssn doesn't validate, then error:ssn above will fire // If the value has changed, then change:ssn above will fire var ssn = prompt("Enter " + employee.get('fullname') + "'s SSN (XXX-XX-XXXX)", employee.get('ssn')); if(ssn != null) // Not Cancel employee.set('ssn', ssn);
initialize/constructor new MooVeeStar.Model([object])
set model.set((key, value)|(object), [silent])
Overloaded set method to set a property or properties on the model. Can be called with a single key/value, or an object with many keys/values. Options can have a silent key that, when true, supresses events. model.set('name', 'Edward', { silent:true }); // or... model.set({ name:'Edward' }, { silent:true });
get model.get(keyOrArray, [raw])
Overloaded get method to get a single value or a map if an array is passed. Passing raw as true will get the raw value directly as it’s stored, without checking if there’s a custom getter within the Model’s properties
getId model.getId()
Returns an id for the model. If the idProperty exists, it will return it, otherwise it will return a unique string. If there is no cid set yet, it will permenently assign it at this time. MooVeeStar.Collection uses this to identify models within itself.
unset model.unset(keys, [silent])
Accepts a list of keys and passes them to Model#set with a null value.
destroy model.destroy()
Destroys a model by setting it’s property to an empty map and firing a destroy event
toJSON model.toJSON()
Returns a recursively cloned value map of the model’s raw properties.
cid model.cid
An internal unique identifier. Lazily set in getId(), it will be assigned a unique string if the model has no idProperty value
changed model.changed
An array of all the changed properties of the last set call
errors model.errors
An array of all the properties that had an error of the last set call
MooVeeStar.Collection
errors collection.modelClass
The class of models will auto-instantiate to when added if not an instance of MooVeeStar.Model already
constructor/initialize new MooVeeStar.Collection([models], [options])
add collection.add(items, [options])
Adds an item to the collection. Will not allow duplicates unless the collection's allowDuplicates is true. Will instantiate the collection's modelClass if the item added is not a MooVeeStar.Model. It is possible to add mixed Model instances. Fires Collection#change and Collection#add and Collection#error events.
remove collection.remove(indexOrModels, [options])
Removes a model by index or all instance of a model or array of models from the collection. If indexOrModels is a number, then remove at that index; if it is a string id, then get the model and remove all instances of it; if it is a model or array of models, then remove all instances of each. Fires Collection#change and Collection#remove events.
empty collection.empty([options])
Empties the Collection by callng remove on all items
move collection.move(indexOrModel, to, [options])
Moves a model from one index to another. If indexOrModel is a number, then move the model at that index. If it is a model then only whose first instance of the model in the collection will be moved.
getId collection.getId()
getLength collection.getLength()
Returns the number of items in the collection
at collection.at(index)
Returns the model at a specific index, if it exists.
get collection.get([key])
Returns the first model found from the key. If key is null, return all items; or call Collection:findFirst with key; or if key is numeric call Collection:at
getAll collection.getAll()
Returns the list of models
find collection.find(values, [keyToFind])
Accept a value or list of values and returns any models who’s idProperty is within the values list. Pass keyToFind to compare that key’s value instread of idProperty
findFirst collection.findFirst(value, [keyToFind])
Returns the first model whos idProperty (or keyToFind value) matches the passed value
toJSON collection.toJSON()
Returns a _new array_ of all of the Models.toJSON values
destroy collection.destroy([options])
Silently empties the list and fires a destroy message
applyToModels collection.applyToModels(operation, opArgs, modelsOrFindValues, findKeyToFind)
Calls a method on all models, or all models found // Set all models to complete collection.applyToModels('set', ['complete', true]); // Set all models whos 'complete' key is === false collection.applyToModels('set', [{ complete:true }], false, 'complete'); // Unset 'complete' and 'due' on models whos 'complete' key is === false collection.applyToModels('unset', [['complete','due']], collection.find(false, 'complete'));

Array Methods

forEach each every invoke filter map some indexOf contains getRandom getLast

MooVeeStar.View

MooVeeStar views help you take control of how your interface interacts with user input and data changes. It was built to take full power of the MooVeeStar.Templating system, but can be used with any templating library.

Extending

You will be creating your own, extended views by overriding some of the properties and methods. If you're using the MooVeeStar templating system then all you really need is to define a template name. Here's a simple view that renders a template, with a couple events:

var EmployeeIdView = new Class({ Extends: MooVeeStar.View, events: { 'model:change':'render', 'click:relay(button.delete)':'destroy' }, template:'employee-id-card' }); $(document.body).grab(new EmployeeIdView(existingEmployeeModel));
constructor/initialize new MooVeeStar.View([model], [options])

The MooVeeStar.View constructor sets a few things for us upfront. As you will likely be overriding the constructor when defining your own view, the placement of your parent call could be important.

  • Sets and merges the passed options object to the default options
  • Sets the model property to the passed model, if it is not previously defined
  • Inflates the template from the Views template and sets it to the element property as well as elements.container
  • Attaches the events, unless options.autoattach is false
  • Calls render, unless options.autorender is false
options view.options
MooVeeStar.View implements the MooTools Options methods by default, and comes with a few options to run itself upfront, usually overriden when defining your own, extended view. { autorender: true, // Automatically call the render function on initialization autoattach: true // Automatically attach events on initialization inflater: MooVeeStar.Templates.inflate, // The template inflate method. Called as: "inflater(this.template, null)" // by default when intializing the element binder: MooVeeStar.Templates.bind // The template binder method. Called as: "binder(this.element, model.toJSON())" // by default in "this.render" } autorender
events model.events

Events are what automate your view's interface with user input, data changes, interface changes... really anything. If it can fire an event your view can listen to it. You can bind events to any property that is defined in your constructor before calling this.parent(). The event object is a set of string event-keys, with string function names.

How's it work?

The events are somewhat magical. There is a lot you can throw at it, and it will only fail when it can't find what you're trying to attach to, or the string method name doesn't exist on your view. The event string syntax can be generalized as 'attachmentObject:eventToListen'. The attachmentObject should be: an existing property of the view, this, window, document, MooVeeStar, an existing element html-id, or an object on window. If you do not supply an attachmentObject, then it will attempt to use the this.element only if the eventToListen is an Element.NativeEvents, otherwise it will listen to the view's own events. Here's some examples:

var MyView = new Class({ Extends: MooVeeStar.View, events: { 'model:change': 'render', // Listen for 'change' events on 'this.model' 'this.model:change': 'render', // (Same as above) 'model:change:name': 'render', // Listen for the specific 'change:name' events on 'this.model' 'click': 'onClick', // Listen for 'click' events on 'this.element' 'element:click': 'onClick', // (Same as above) 'this.element:click': 'onClick', // (Same as above) 'this:click': 'onFiredClick', // Not a mouse-click! Force listening to an internal view event // named "click" fired through 'this.fireEvent("click")' 'click:relay(button)': 'onButtonClick', // Listen for 'click' events on button children of 'this.element' 'elements.button:click': 'onButtonClick', // Assuming 'this.elements.button' was set before attaching events // this will listen for clicks on this element 'someProperty:some-event': 'someFn', // Assuming 'this.someProperty' exists and has an 'addEvent' method 'this.someProperty:some-event': 'someFn', // (Same as above) 'window:scroll': 'onWindowScroll', // Window Scroll event 'document:click': 'onWindowScroll', // Click events on the 'document' 'MooVeeStar:some-event': 'someFn', // Listen for 'some-event' fired through the MooVeeStar mediator 'someGlobalObject:some-event': 'someFn', // Assuming 'window.someGlobalObject' exists and has an 'addEvent' method 'someHtmlId:keydown':'onSomeKeydown' // Assuming an element with 'id="someHtmlId"', listen for it's keydowns }, initialize: function(model, options){ // Need to define these per the above before calling this.parent this.elements.button = $('aButtonsId'); this.someProperty = new ClassWithEvents(); this.parent(model, options); // Auto attaching events }, /* the defined event callback methods from above */ });

A view error will be thrown if: You attempt to attach events to something that does not exist or does not have a addEvent method; or the listeners callback method name does not exist on your view.

template view.template
The string key of the template to use. Can also be an element. The template will get inflated and set as this.element
model view.model
The view model. A view can have as many models as you would like and all can be used in any facility. A view.model will be setup by default in the view constructor.
element view.element
The element inflated from view.template
elements view.elements
A map for elements to be cached to for use in either the events map, or in other view methods. The view.element is added automatically as view.elements.container as well.
render view.render([data])
Likely overridden and called through parent(), this determines the data to bind to the element. Pass a MooVeeStar.Event instance which contains a changed key, it will only rebind those changed values. Otherwise, it will use the passed argument or, more likely, call model.toJSON
toElement view.toElement()
Returns the this.element. Automatically called by MooTools when the view is used as an element, like: $(myView) or document.body.grab(myView)
destroy view.destroy([element])

Destroy the element, detaching all events and any child elements with view controller's events.

You should ensure to destroy your views by calling view.destroy() and avoid removing the element directly, either through this.element.destroy(), or someParentElement.empty(), etc. Doing so will not detach all event listeners.

empty view.empty([element])
Empties the passed element, or the view’s element. Calls view.destroy() for all attached views within the element’s DOM. Fires the View#empty event
dispose view.dispose([element])
Disposes of an element, or the view’s element. This does not affect and attached sub views (like destroy and empty). Fires the View#dispose event
attach view.attach([element], [excludeSelf])
Attaches all the events in the this.events map, as well as attaching all child elements with a view controller
detach view.detach([element], [excludeSelf])
Detaches all the events in the this.events map, as well as attaching all child elements with a view controller