The design principle of viaProxy
[update: the library is now in github, and the sample code discussed in this post is here]
viaProxy is a client side JavaScript library that can be used to build complex, fast and fluid web UI yet in a manageable code complexity. It is a set of low-level api which is built on top of jQuery and enable you to synchronize your view and model using imperative programming (code only) or declarative programming ( mark-up only) or both. You can use it to write testable views, modules, plugins, and aggregate them into complex view.
About a year ago, that is after I wrote jquery-matirx, I had been thinking a problem in a project. We were developing a rich web UI using JavaScript intensively , but as the logic of application grows, the JavaScript code explode, and become buggy and spaghetti like, as our logic is distributed every where in the handler. Although, I create a solution to dynamically load view into the page at client side with some template, but does not solve the issue completely. So I think, is there a UI framework to address this issue systemically. I studied libraries like Backbone.js and knockoutjs , which seems to be good UI framework. Initially, I thought I had sufficient JavaScript competency to use them, it turned out I had lots of difficulties to write a "hello world" app with them, and even greater difficulties in solving a real world scenario. The syntax and convention are just too cryptic for me, and it is hard for me to do unit test and debug. So I decided to write my own, that is how I started viaProxy
Before I discuss how to use the library, I want to put the design principle of the library upfront, which is fundamentally important to use the library.
The principle is to separate model interaction from model presentation.
You can think of model interaction as CRUD(CREATE, READ, UPDATE, READ), and model presentation as view rendering. A view is the typically the UI that user can see and feel, a model is typically business object of your application. I want to use the word "typically", because sometimes, the difference between view and model can be blurred. A view can be just another model, we will discuss this in the future post.
Let's take a look at "Hello world" application to understand how model and view play together. The application allows user enter a name, and click a button, the application display a greeting message. Here is markup that defined the view.
<label>Please input your name: <input type="text" id="txtName"/> <input id="btnGet" value="Get message" type="button"/> </label> <div id="divMessage"/>
And here is a piece of javascript to implement the user story.
$(function () { $("#btnGet").click(function () { var name = $("#txtName").val(); if (name) { $("#divMessage").text("Hello," + name); } else { $("#divMessage").text(""); } }); });
You might be laughing at how stupid the code it is. But there is nothing wrong with it, in fact, it is simple and elegant, if this is everything that the user want from the app. However what user want is far more complex web application. If you have a little bit web UI programming experience, you probably agree that, by applying this style of implementation to develop complex UI, the code will soon explode and become spaghetti.
Let's take a closer look. In the click handler, it by-pass the model and directly update the view, in fact it does not even define a model, it just directly convert user's click action into updating view. When user stories grows, we need to create more viewsto implement them, and we need more view handlers to take care of user interaction. Our view handler will have hundreds or thousands lines code doing thing like "If user input this, do this, if input that do that, because of this change, we also update an this view, because that change, we also need to update that." Very soon, the code complexity will grow out of control. So what is wrong? The fundamental fault is mixing model interaction with model presentation, because our view handler has too much thing to worry.
Let's refactor the above code using viaProxy. At first sight, the refactory may require you to write more code, what I show is how viaProxy works under the hood, I will introduce higher level viaProxy API which can reduce your code to minimum or no code at all. The first step is to define a model, which is missing from the previous implementation. Without model, we can not possibly separate a model interaction from model presentation. Model is center of an app. So here is the model.
var rootProxy = via(); var helloAppModel = { name: null, greeting: "Hello", message: function() { return this.name ? this.greeting + "," + this.name : ""; } }; rootProxy.insert( "helloApp", helloAppModel);
The model is a javascript object which has 3 members name, greeting, and message. The message function combines greeting and message. It is very pure and simple, that it does not inherit from anything other than object, it does not use hard-to-understand concepts like "Observerable", "viewModel", which are used in other framework. As your can see, we put our model into a repository using a proxy with a namespace "helloApp", this prevents you from direct access to the model, all access must be via the proxy, this is why the library is named viaProxy.
Now let's take on model interaction. Instead of using raw DOM event directly, we wrap it into higher abstraction, view event. View event can be one-to-one mapped to DOM event, but it can be customized event. For example, we can create an "enter" event which will trigger when "keypress and keycode === 13" event triggers, I will cover that later. Here is how we attach a view handler to the view event, like below.
$( "#btnGet" ).addViewHandler( "click", "helloApp.name", function( viewContext ) { var value = $( "#txtName" ).val(); viewContext.updateModel( value ); //or //via("helloApp.message").update(value); } );
The view handler is also associated with a path("helloApp.name") which is pointer to the model in repository. In the view handler, it does only one thing, updating the model with the user's input, however it does not care about updating the divMessage, because this is the job of model handler. This is a big difference from the previous implementation. Remember, model handler is only place do model presentation. Here is the model handler to update the divMessage.
$( "#divMessage" ).addModelHandler( "helloApp.message", "afterUpdate", function ( modelContext ) { var value = modelContext.currentValue(); //or //var value = via("helloApp.message").get(); //"this" refer the divMessage $( this ).text( value); } );
The semantics here is when model event "afterUpdate" happens to "helloApp.message" in the repository, call this handler to update view "divMessage". In the model handler, it also does only one thing, update the view. You maybe notice that the previous view handler update "name" property, why the event of "afterUpdate" is raised for "message" property, this is because viaProxy knows this dependencies between "name" and "message". Here you don't need to write some thing like "observerable()" which is used by other library. After creating the model, adding a viewHandler, a modelHandler, your "hello world" application is up and running.
So what is the big deal of separation model interaction from model presentation. It might not be obvious for you right now. But here is a few things that I can think of. Firstly the separation can reduce code complexities to be manageable. As your application complexities grow, your code will grow linearly but not exponentially. You can add more view handlers to a view event to update more model, and add more model handler to model handler to update more views.
Secondly, the separation make each handler focus on one thing, so that code is more reliable, more testable. Yes, we can unit test our UI. I will cover that later. The idea is that we can test our view handler by triggering fake mouse event without a real mouse click. Because view is rendered by model handler, to test a view is to test model handler. We can test model handler by updating our model using via proxy.
Thirdly, you have a explicit, complete model, a single copy of logic, not a duplicated partial model distributed in spaghetti code. And the model can scale, you can add more model into different name space. The model the king of your UI, which rules the application. Different parts of model become shareable, and connected. The code become more easy to understand because of this.
There are other creative uses of viaProxy. For example, in our ajax callback, instead of update the UI directly, ajax callback update model using viaProxy, and your UI will be updated indirectly in a model handler. Instead of making ajax call directly in your view handler, your view handler can update model using proxy, that will trigger model handler, which will make the ajax call.
Through the "hello world" example, we can see that the design principle of viaProxy is to separate model interaction from model presentation. Essentially, it provides three following mechanisms to facilitate the separation.
- A proxy to build and access models
- An extensible model event mechanism to connect model to view
- An extensible view event mechanism (based on jQuery event) to connect view to model
Now that we know the design principle behind viaProxy, what is next. There are still lots of challenge. Does it work in a real world scenario or in large scale implementation, does it work with other existing controls or plugins, what if my model is complex object like array instead of simple string or number, is it extensible, how about validation, does it encapsulate too much or too little, is it too automatic, how easy is it to customize it, can I continue to use my programming style while using viaProxy, what about learning curve? I have considered all these when I develop the library, and I will discuss more in the future posts. Let me a comment, tell me what you think and stay tuned.