Great Hack: Writing an MVC Framework From Scratch
Overview
MVC frameworks are a design pattern commonly used when programming modern Graphical User Interfaces (GUI). There are dozens of well-written, well-supported frameworks out there, so it is not to say that the world needs yet another one, but creating a new one is an excellent exercise in architecture, planning, and testing. That is why my most recent Hack Week project was to design and build my own MVC framework for use in my own projects.
Photo credit: Wikipedia.org
What is a MVC Framework?
An MVC framework stems from the popular object-oriented programming paradigm called Model View Controller. The strength of this pattern can be easily summarized by the fact that it ensures the separation of concerns, where each individual component is responsible for a specific set of functionality; in this case a model is tasked with managing and storing data, a view is responsible for rendering the visual (display) aspect of the site, and a controller is essentially the brain, taking user input from the views and manipulating data on the model. None of these components should be tightly coupled, providing an environment of interchangeable pieces.
Meet Wraith
Wraith was a project I thought up several months ago out of my frustrations with the current MV* frameworks available on the internet. I was working on a few small, single page applications and was testing different frameworks to see which suit my needs. I used Backbone, Spine, Angular, and a few others that didn’t quite fit the bill. What I wanted was a framework that bound the data to the view, something I call implicit model-view binding, but required no logic to be present inside the views.
For all intents and purposes, Angular does provide this level of functionality, and so does Backbone, with the help from a variety of different plugins. But Angular is rather big, has a pretty steep learning curve, and doesn’t enforce logicless views (something I feel is extremely important in such a framework), and Backbone takes a bit of finagling to get anything to work quite right. Additionally all of these frameworks work best when used with a library like jQuery, or Zepto to handle event delegation and DOM manipulation.
Why make another MV* Framework?
I wrote Wraith because I wanted a MV* framework that didn’t depend on any external libraries, had Angular-like Model-View binding, and was super lightweight and easy to understand. Additionally I wanted to write this framework in CoffeeScript since it is easy to read, has powerful array comprehension, and is just a ton of fun to write in.
Along the way I sought inspiration from Spine, Backbone and Angular, mixing Spine-style Models and Collections, Angular style directives, with Backbone-style templating (a la Handlebars). All of these inspirations make Wraith a unique experience, but it still feels incredibly familiar to most frontend developers.
What makes Wraith different?
Wraith is completely self-contained. You need nothing else to get started creating a basic single page application. I say basic because the framework is very much in its infancy. It does not have support for URL routing, AJAX requests, animations, or persistent storage. These are all things I hope to accomplish in the near future.
Now that I have identified what Wraith doesn’t have (yet), lets talk about what it does well:
- Implicit Model-View binding (akin to the MVVM design pattern)
- Controllers that are also views (again, MVVM)
- Handlebars-esque logicless templating
- Template declaration directly in the DOM that doesn’t require a compilation process
- Event binding directly from the DOM, instead of requiring JS to do so
- Partial view updating (only update elements that changed)
- Well under 20kb when minified
How to get started with Wraith?
Wraith is declarative, in that much of the heavy lifting – data and event binding, class and text manipulation – happens directly in the markup (HTML). Your controller is initialized from the DOM directly, so when you create your app it’ll look something like this:
<section data-controller=”App.MainController”> … </section>
Wraith will require you to create an App.MainController object in the global namespace, which it will find and create an instance of, binding it to the element that its defined on (in this case, section).
CoffeeScript:
App = {} class App.MainController extends Wraith.Controller constructor: -> @registerModel(App.List, 'list') # Register our model as 'list' onKeypress: (e) -> alert(e)
JavaScript:
var App = {}; App.MainController = (function(_super) { function MainController() { _super.call(); this.registerModel(App.List, 'list') // Register our model as 'list' } MainController.prototype.onKeypress = function(e) { alert(e); } return MainController; })(Wraith.Controller);
Before Wraith will do anything though, you must initialize its bootloader. This will start the controller initialization.
<script type=”text/javascript”> new Wraith.Bootloader(); // My app is starting! </script>
Event Handling
In Wraith, you are required to create event handlers in your controllers, but you bind them to events inside the DOM structure like so:
<section data-controller=”App.MainController”> <input type=”text” data-events=”keypress;onKeypress" /> <div data-bind=”list.items” data-repeat> {{text}} </div> </section>
Now when the text input is typed into, the onKeypress method on App.MainController will be invoked.
Models and Collections
I really enjoyed working with Models in Spine when compared to other frameworks, and thus Wraith’s models are similar in design. You can create a new model with default values easily:
CoffeeScript:
class App.ListItem extends Wraith.Model @field 'text', { default: 'New Item' } @field 'selected', { default: false }
JavaScript:
App.ListItem = (function(_super) { function ListItem() { _super.call(); this.field('text', { default: 'New Item' }; this.field('selected', { default: false }; } return ListItem; })(Wraith.Model);
Collections can be done similarly:
CoffeeScript:
class App.List extends Wraith.Model @hasMany App.ListItem, 'items'
JavaScript:
App.List = (function(_super) { function List() { _super.call(); this.hasMany(App.ListItem, 'items'); } return List; })(Wraith.Model);
Data Binding
One of the most important things I tried to accomplish with Wraith was easy data binding. I didn’t want to write logic in my views, so I needed to handle looping over collections as well as showing and hiding views or partial views. The solution was to allow a view to be bound via dot-notation to a property on a model similar to what Angular does.
<section data-controller=”App.MainController”> <input type=”text” data-events=”keypress;onCheckboxKeypress” /> <div data-bind=”list.items” data-repeat> {{text}} </div> </section>
This will bind the input to the list property on your controller (App.MainController). Every time the list.items property changes, the view will automatically be updated (and in this case, repeated as a list).
Class Binding
Want to hide or show something? Instead of writing logic in JavaScript to hide and show an element or alter its class attributes, you can use data or methods from your models to alter the class structure.
<section data-controller=”App.MainController”> <input type=”text” data-events=”keypress;onCheckboxKeypress” /> <div data-bind=”list.items” data-repeat> <span data-class=”highlight:selected”> {{text}} </span> </div> </section>
When selected is true, the class highlight will be applied to the span surrounding our text.
Want to know more?
This was just a brief overview of what Wraith is capable of doing right now, and what it will be capable of doing in the future. For more information, follow me on Twitter (@Shaun_Springer), check out the github repository, read the documentation, and check out the examples below: