seanmonstar

Aug 23 2012
Jun 1 2012

Shipyard 0.2

The 0.2 version of Shipyard is out and about. Besides the usual minor bug clean-up, I wanted to share all the brand new toys it’s got.

  1. ObservableArrays
  2. CollectionView and SelectView
  3. Model Relationships
  4. Logging

If you’re like, “Show me naow!”, you can play over on jsFiddle. Or maybe you like changelogs.

ObservableArrays

The major players in Shipyard (Models, Views, and Controllers) are all Observables, which is what makes live-data-binding to Views available. However, binding to data wouldn’t work well if that piece of data was an array (like an array of Models). The ObservableArray mimics an Array, but extends from Observable. So, you can observe any index on the array, or the length.

The most useful part is that you can observe the array property of the ObservableArray. This property event will always emit the index of the change, an array of the removed elements, and an array of the added elements.

var arr = new ObservableArray('Foo', 'Bar');
arr.observe('array', function(index, removed, added) {
    console.log(index, removed, added);
});

arr.shift(); // log: 0, ['Foo'], []
arr.push('Baz', 'Quux'); // log: 1, [], ['Baz', 'Quux']

CollectionView and SelectView

With the use of the ObservableArray, CollectionViews are a whole lot better. You no longer need to manually manage them by with addItem. If you create an ObservableArray, and pass it as content to the CollectionView, it will update it’s childViews whenever you modify the ObservableArray instance. You can also pass a regular array, and later on .get('content').push(myModel), and the CollectionView will update automatically.

var arr = new ObservableArray('Foo', 'Quux');
var list = new ListView({ content: arr });

arr.splice(1, 0, 'Bar', 'Baz');
// list view now has child views in order of
// 'Foo', 'Bar', 'Baz', 'Quux'

SelectView is a new CollectionView available, with an additional computed property, selected, to observe which value on the content array is selected.

Model Relationships

Two new Fields are available, ForeignKey and ManyToManyField, that allow you define relationships between models. ManyToManyFields also take advantage of ObservableArrays, letting you know when a Model’s list of related models change.

var tags = task.get('tags');
var tagsView = new TagList({ 
    content: tags,
    // TagList is fictional, so is onTagSelect
    onTagSelect: function(tag) {
        var index = tags.indexOf(tag);
        if (index >= 0) {
            tags.splice(index, 1);
            // Task.tags has this tag removed
            // and also automatically updates this ListView
        }
    }
});

Logging

The tiny logging utility has morphed into a serious logging module very similar to Python’s own logging module. It’s still very easy to use quickly:

require('shipyard/logging').info('an info message');

However, it also comes ready to customize completely. You can specify different formats, different log levels, and different handlers for various loggers. Specifically, you might only care about shipyard internal logging that is of level WARN and above, but want DEBUG and above from your own app. In addition, you might want to pass any and all ERROR logs to your server, so you can see whenever your JavaScript is causing errors on other computers.

It would all be something like this:

var config = require('shipyard/logging/config');
config({
    handlers: {
        'console': {
            'class': ConsoleHandler
        },
        'xhr': {
            'class': XHRHandler,
            'level': 'ERROR',
            'url': '/api/log'
        }
    },
    loggers: {
        'shipyard': {
            'handlers': ['console', 'xhr'],
            'level': 'WARN',
            'propagate': false
        },
        'myapp': {
            'handlers': ['console', 'xhr'],
            'level': 'DEBUG',
            'propagate': false
        }
    }
});

Get it

It’s on GitHub, as always. It’s also available from npm. And it’s in the dropdown on jsFiddle. So yea.

Mar 21 2012

Shipyard 0.1

I’ve been working on a JavaScript MVC framework for past several months called Shipyard. It’s an incredibly modular framework, with ease of testing, and all that other kool-aid. What’s important now is that this is the first release point for Shipyard. Here’s what it comes packed with:

  • Models
    • Syncs to various locations
    • Fields to serialize data easily
  • Views
    • Automatic updates using Bindings
    • Uses EJS templates underneath
    • Templates get pre-compiled for production, so templating engine isn’t needed in final file.

There are plenty of other modules included, but not all have public docs currently as many of them aren’t needed specifically by app developers, only by the internals of Models and Views.

It’s already powering Mozilla’s Add-on Builder.

Start Playing

You can read more about Shipyard at the docs page, play with the obligatory example to-do app, or the brave can start at the source.

Or, you can grab your require and try it out in jsFiddle right now.

To use it locally, you could check it out with git, but it’s also available on npm, via npm install shipyard.

What’s Next

With this release, work begins on version 0.2, which will bring about Model Relationships, QuerySets, some Controllers, and a configurable logger.

Nov 30 2011
Nov 21 2011

Past Month of Shipyard

Last month I got to start working on Shipyard almost full-time, as I need it to make Add-on Builder work by the end of the quarter. I’m not ready to call Shipyard 1.0 until I’m confident with the API for Models, Controllers, and Views. Models are largely done, Views need some work, and Controllers need a start. Besides that, though, what has happened to Shipyard in the past month?

Here follows a changelog-ish list.

  • Animations
    • A merge between MooTools’ Fx.Tween and Fx.Morph classes
  • Events
    • addListener returns a Listener object with attach and detach methods.
    • support for once
    • legacy API
      • official API for EventEmitters is addListener, removeListener, and emit
      • previous methods will soon log a DeprecationWarning, before being removed by 1.0
  • New utils
    • Logger
      • Makes the console more familiar for Python users
      • Doesn’t error if there is no console available
    • Cookie
      • Thanks to MooTools
    • Color
      • Thanks to MooTools
  • DOMEvent
    • Thanks to MooTools
  • Env
    • provides browser and platform
  • A briefer test runner
    • Only prints dots for successes, F for failures, and prints errors at the end

If commit lists are more your thing, you can look to GitHub. The next month should see event delegation, and more work on the View/template system.

Sep 19 2011
Oct 18 2010

MVC in MooTools: Controllers

This is another installment about how to use my MooTools MVC framework. If you’re interested in the previous parts, check out my write-up on using Models and Views.

It’s often explained that Controllers are how you tie Models and Views together. I don’t want to get into that argument. For me and my framework, Controllers handle DOM events. They listen to the views for events, update models, and then render new views. Let’s take a look.

Event.Delegation

Event delegation is used throughout the controllers, since it greatly eases the pain of changing the DOM around all the time. When you name a controller, the first part of the name will be used to find or create an element to look over, unless another element is specified. Example time:

var RecipesController = new Class({
    Extends: Controller
});

//attaches itself to <div id="Recipes"></div>
//if that doesn't exist, it gets created

The rest of the controller contains event handlers for everything that goes on inside that element, and methods that various event handlers can call.

var RecipesController = new Class({
    Extends: Controller,
    events: {
        load: function() {
            Recipe.findAll(this.list.bind(this));
        },
        'click:relay(.recipe .delete)': function(e) {
            e.preventDefault();
            var recipeView = $(e.target).getParent('.recipe');
            recipeView.get('model').destroy(function() {
                recipeView.destroy();
            });
        }
    }
});

The load event will fire on domready, and eveything else is in the MooTools event delegation way. Currently, all event handlers are bound to the controller instead of the element, since it’s easy enough to get the element from the event object, and you’re most likely going to want to call a method of the controller. For instance, you might notice that the load event has a call to list on the controller.

this.view

First-class methods on a controller get access to the view method. This is a convenience method that will instantiate views for you, with a little auto-magic thrown in.

var RecipesController = new Class({
    Extends: Controller,
    // events: {},
    list: function(recipes) {
        $(this).set('html', this.view({ recipes: recipes }));
    }
});

view can accept 2 parameters: the view name, and a data object. However, you can exclude the view name, and it will try to use the controller and method names. In the list method above, since no view name is passed to view, it will try to load recipes/list.

That’s About It

That’s all there is to Controllers, actually. They just handled events fired by Views, let appropriate Models know, and change the View if necessary. Although, while the role seems small, it tends to contain the majority of logic anyways. Models just hold on to their data, and Views just show it, but Controllers are how you manipulate it.

Sep 2 2010

MVC in MooTools: Views

This is another installment about how my MooTools MVC framework rocks. Check it out. If you’re interested in the first part, check out my write-up on using Models.

After you make the building blocks of your application, using Models, Views are how you display that information to a user. Having written a lot of JavaScript that adds content to the page, I know just how much it can suck. You typically have two options:

  1. Concatenate a huge string of markup with variables, then setting the innerHTML of a div.
  2. Use a bunch of document.createElement and el.appendChild, ad infinitum.

Both are equally unappetizing. Wouldn’t it be so much nicer to write your views like we do in other languages?

Templating

Here’s a nice mix of HTML and JavaScript. No tricky string concatenation1. You just have to put your JavaScript inside tags like you would in PHP.

<ul id="Tasks">
<% tasks.forEach(function(task) { %>

    <li class="task">
        <input type="checkbox" />
        <%= task.get('title') %>
        <% if(tag.get('tags').length) { 
            print(view('tasks/inline_tags', { tags: task.get('tags') }));
        } %>
    </li>

<% }); %>
</ul>

View.Helpers

Besides print, which does exactly what you think it does, there are a bunch of other functions that are sort of “globally” accessible from a View. And these are functions defined on View.Helpers. The most basic version of the helpers only includes a view function, which let’s you nest views inside each other, like I used above. If you include the View.Helpers file, you get more functions, such as excerpt (truncates to a certain length) and date (allows date formating similar to PHP’s date method). As I find more basic functions that you would want in a templating system, I’ll add more, such as possibly something like escape.

The File

These are saved as .html files. You don’t need to manually include them like Models or Controllers; the View class will first check to see if they’ve been included, and if not, go and fetch them over the wire. And they go in you app’s views folder. The example above would have a structure like so:

js/
    views/
        tasks/
            list.html

The View Class

The View Class is what converts templates into views for your application. You can instantiate a View, and then pass it data and ask for it to process itself using the passed data.

var myView = new View('tasks/list').render({ tasks: myTasks });
myEl.grab(myView);
myOtherEl.set('html', myView);

Views have both a toElement and toString function defined to return the processed mark-up in the proper format. This means you can simply pass the view as a parameter to most MooTools Element methods, and it just works2.

Good news, though: Controllers have a convenience method called view that really makes this a joy to use. I’ll have more to say about Controllers in my next report.


  1. The string concatenation is done internally. Everything in the template is turned into a string using John Resig’s micro-templating suggestion. 

  2. Regular DOM methods might not though. appendChild expects an Element, and since it’s not part of MooTools, it’s not rigged up to called toElement. Also, because of the way toString works with Classes in Internet Explorer, this feature doesn’t work in those browsers. MooTools 1.3 repairs that problem. 

Aug 25 2010

MVC in MooTools: Models

It’s really bugged me when writing large JavaScript applications, organization of code never really seems to be considered. We use frameworks for all the server-side stuff we do, but everyone seems content writing JavaScript in one big mess.

Granted, that’s why I was attracted to MooTools in the first place: the idea of organizing your code is built into the framework. If you don’t predominantly write classes with MooTools, you might as well be writing jQuery. But even with classes, in a JavaScript application1 you write a lot of code that works with those classes and fiddles with the DOM. It can get to be a real mess.

A year ago, I got fed up with it. I was also interested in Adobe AIR at the time, and liked the idea of being able to copy over as much of my code as possible to make an AIR version of an application. Hopefully, the only changes would be the way data was stored and received. I feel pretty good when using the MVC paradigm on the server-side, and I felt it made sense in the browser as well. So I started a MooTools MVC framework.

Models

I started with making various sub-classes of Model2 that interacted with specific data storage concepts. In Adobe AIR, a SQLite database would likely be used. In the browser, you’d likely have you’re data stored on the server, and need to access it via ajax. I wanted to be able to have the same model class, and just change its subclass based on need. Certainly, the Model.SQL could be used in newer browsers, as well, if you wished.

It didn’t take look for me to give up for the time being on AIR, but I still felt that the organization that such a framework provided was beneficial. Therefore, I spent more time on Model.Ajax and Model.Browser, since most of my use of the framework has been for the browser. Model.Browser uses a combination of localStorage, userData, and cookies, depending on support, to allow a web application to function entirely on the client side. I did much of this just to allow myself to work out an example application, without needing a back-end.

Model.Ajax is what I use the most, now. Let me show you an example model:

var Task = new Class({

    Extends: Model.Ajax,

    fields: {
        id: Model.Fields.AutoField({ write: false }),
        title: Model.Fields.TextField(),
        created_at: Model.Fields.DateField(),
        tags: Model.Fields.ManyToManyField(Tag)
        is_done: Model.Fields.BooleanField({ default: true })
    }

});

By extending Model.Ajax, Task will make some default assumptions about the back-end API. For instance, Task.find will call /tasks/find, Task.get(5) will call /tasks/5, and myTask.save() will call /tasks/insert or /tasks/5/update depending on if it’s new or not. And of course, because of the asynchronous nature of all these function calls, a callback can be passed, which will receive the instances as it’s only parameter upon completion. Such as:

Task.get('123', function(task) {
    console.log(task); //the returned data, cast as an instance of Task already
});

A model also declares its fields, so that every time a new Task is made with a hash of properties, it only extracts the ones it cares about, and converts them to the proper type if it can. Specifically, I’ve set id to read only, since I don’t want something accidentally changing the id of a task and sending it.

An instance of a Model can access it’s properties through getter and setter functions. These are similar to the Element getter and setters, and they allow each individual field type to cast to the proper type, or return a default. For example:

var myTask = new Task;
myTask.set('is_done', 'true');
console.log(myTask.get('is_done')); //will return the boolean true, not the string.

This also allows Fields that provide relationships to other models cast ids to instances and back again. Task.tags will store an array of tag ids internally, and submit those to the server upon a save. But if you get the tags, the ManyToManyField will convert them into the existing Tag instances.

The point of using Models in this framework is to have a single location that is responsible for abstracting data retrieval3, and providing a central place to handle any data conversion. You shouldn’t have any Ajax requests or data validation any where else in your application.


  1. From here on out, my use of a JavaScript application is one where a large amount of it is written in JavaScript. Sure, your requests still need to interact with a server-side technology, but on the client side, most of the application functions without page refreshes. 

  2. The assumption is made that you know what MVC is. If not, read up

  3. Whether the Model or the Controller should do the ajax requests has been argued both ways when used in a JavaScript MVC framework, . Since the primary reason for a Model is to abstract the data storage system, it makes sense to me that only the Model should know if it needs to access a web server via ajax, or use localStorage, or use SQLite.