-
Notifications
You must be signed in to change notification settings - Fork 280
Enyo MVC Intro
- Background
- MVC in Enyo
- Design Goals
- Overview
- Bindings
- Controllers
- Models & Collections
- MVC Bootplate
- Where Things Stand
Separation of concerns is a fundamental principle of good software design. Generally speaking, you'll write better and more manageable code if you break your project up into discrete modules with well-defined purpose and scope, and strive to minimize redundancy and dependencies between modules.
If you've spent much time with Enyo, you've probably noticed that we talk a lot about modularity and encapsulation. Enyo promotes separation of concerns by helping you factor your application into components. Each component encapsulates a subset of your app's functionality and exposes it to other components via a public API (a set of properties, methods and events).
For example, you might implement a specific part of your app's UI by defining an
enyo.Control
(a kind of component). This control would be responsible for
"composing" itself out of simpler components, rendering itself into the DOM,
capturing user input events, interacting with its parent and child components
using their APIs, and cleaning up after itself when its work is done.
Experience has shown us that apps made from thoughtfully-designed components are easy to test and easy to modify to meet changing needs. In addition, they often yield building blocks that can be reused in other projects or shared with other developers.
There's more than one way to separate concerns, though. While Enyo has always made it easy to organize your app along functional lines, it hasn't historically done anything to help you isolate app data and business logic from presentation. It's actually fairly common for a single Enyo component to compose a view, handle input, manipulate data and update application state.
What if you want to keep these aspects separate? That's where MVC comes in. MVC (Model-View-Controller) is a well-established pattern for separating an app into distinct, loosely-coupled layers. While frameworks vary in their specific interpretations of MVC, the basic concepts are essentially the same:
-
Models define the types of data an app deals with and the rules/logic for manipulating data. They are also responsible for interacting with the databases or services that house the data.
-
Views are responsible for presenting data to the user and providing ways to act on that data.
-
Controllers mediate between models and views, manipulating models in response to user actions and updating views when models change.
MVC-style separation of concerns comes with its own set of benefits. Perhaps even more so than the component model, MVC enhances testability—models and controllers readily lend themselves to automated testing, since they're decoupled from UI.
MVC also makes it easier to have specialized roles within your team, with different developers focusing on different layers. In principle, MVC even makes it possible to replace one layer entirely, with minimal impact on the other layers. You might reimplement your models to support a different backend datastore, for example, or build an entirely new set of views to support a radically different type of device.
Not surprisingly, MVC support has been one of our top feature requests since Enyo 2 debuted earlier this year. Over the last few months, we've done a lot of thinking about how MVC might best be expressed in an Enyo context.
We considered a number of possibilities, including doing nothing—after all, we've seen developers employ MVC in Enyo apps without any help from us, often by loosely coupling Enyo with Backbone.js.
Ultimately, though, we decided that there was much to be gained from adding first-class MVC support to Enyo.
Having decided to work on MVC, we set out with the following design goals:
-
Make MVC in Enyo 100% optional. MVC is great, but it's not necessarily for everyone, or for every project. Existing Enyo apps need to function exactly as before, and developers should be free to adopt MVC or not, as their tastes and needs dictate.
-
Maximize the benefits of both MVC and Enyo's component model. Most JavaScript MVC frameworks rely on some form of simple HTML template system for generating views. But much of Enyo's value comes from moving beyond standard HTML elements to reusable, higher-level components that work seamlessly together and encapsulate best practices in structure, styling, behavior and performance. Our solution aims to deliver MVC benefits without sacrificing the goodness of Enyo components.
-
Don't reinvent the wheel. Others have done good work on MVC, and we should leverage this work where we can—especially in areas where there's minimal overlap with existing Enyo functionality.
-
Offer a well-lit path, but be flexible. For developers who want an out-of-the-box MVC solution, we should provide an easy-to-use Enyo MVC app template. But we should also make sure that the pieces of our MVC implementation are useful on their own and in various combinations, enabling developers to mix and match.
Most of the kinds used to support the MVC pattern are provided in the optional mvc library.
The goal of data binding is to greatly simplify the plumbing needed to keep views in sync with their underlying data, whether in the view's published properties, properties on models and collections proxied via controllers, or even properties of two controls inside a single view.
Starting in 2.2, bindings are a first-class feature of the Enyo core, implemented in enyo.Object. With bindings, any two properties of any two instances of enyo.Object (or its subkinds) may be bound either unidirectionally or bidirectionally, such that when the value of the source property changes, the target property is automatically updated with the new value.
Subkinds or instances of enyo.Component may specify bindings via a declarative bindings block, as well as use an API to programmatically create or remove bindings.
In traditional Enyo, event handlers for views and associated logic effectively act as "controller" code; in our MVC pattern, views now have the option to delegate their events to subkinds of enyo.Controller that are assigned to the view.
After defining the models and collections, Enyo Controllers may proxy shared models and collections to multiple views that are kept in sync via bindings.
The "Model" layer is provided by an Enyo-Backbone extension. Backbone's models and collections provide a simple API for loading remote data, observing changes, providing validations, and so on. We've chosen to use Backbone because it's lightweight, feels natural when coupled with Enyo, and has a lot of momentum behind it. Note that Backbone views are not relevant to Enyo, and are not used in our MVC pattern. Instead, our "views" are enyo.Control objects.
The MVC library also includes Collection-aware Repeater and flyweight List controls, which allow you to bind list template properties to model properties of a collection. The Repeater or List will monitor the underlying collection (or any models contained within), re-rendering itself automatically if changes are detected.
Finally, we are releasing a new mvc-bootplate app template to complement the existing bootplate. The mvc-bootplate template is a ready-made generic Enyo app using MVC; it conveniently brings together all the pieces you need to start developing your own MVC application.