
To date, there are a lot of JavaScript libraries to create rich-client applications. In addition to the well-known Knockout, Angular.JS and Ember there are a great many other frameworks, and each has its own peculiarity - someone promotes minimalism, and someone - ideological purity and compliance with the MVC philosophy. With all this diversity, more and more new libraries appear regularly. From the last that was mentioned on
Habré -
Warp9 and
Matreshka.js . In this regard, I want to talk about my own crafts, meet,
JohnSmith - a simple and lightweight JavaScript framework for building UI.
')
First of all, I would like to say that JohnSmith was not written for the sake of some academic interest and not to eliminate that fatal flaw. Quite the contrary, JohnSmith was born in a real project, then migrated from project to project, gradually improving and changing its shape. And now it has materialized as a full-fledged open-source library.
Example
To demonstrate the capabilities of JohnSmith, we will write the simplest application with the following functionality:
There is an input field in which the user writes his name. As soon as the name is entered, we show the message: Hello,% username% .Who wants to immediately see the result: here is a ready
User Greeter .
View Model
Let's start with the creation of the View Model, and first of all, write the "class":
var GreeterViewModel = function(){ }
The View Model usually “exposes” objects to the outside world, changes of which can be tracked from the outside. In JohnSmith, these objects are called bindable. Add a field to store the user name:
var GreeterViewModel = function(){ this.userName = js.bindableValue(); };
This field (
userName
) will be used for bidirectional binding in the View. Add another field that will form the message text. This field depends on
userName
, so we describe it as
dependentValue
:
var GreeterViewModel = function(){ this.userName = js.bindableValue(); this.greetMessage = js.dependentValue( this.userName, function(userNameValue){ if (userNameValue) { return "Hello, " + userNameValue + "!"; } return "Please, enter your name"; }); };
js.dependentValue is similar to computed in knockout, except that we manually specify dependencies in JohnSmith, since there's no auto-tracking magic behind the scenes.
Model View is ready, now we will describe View.
View
Let's start by creating a class:
var GreeterView = function(){ }
A view is a collection of markup and the logic of the connection between this markup and the outside world. The markup is described in the
template
field, and the logic is described in the
init
method:
var GreeterView = function(){ this.template = "... ..."; this.init = function(){
In our test example, the markup is pretty simple, so we write it directly in the template field:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(){
Now go to the
init
method. First, JohnSmith implies that each View works with a specific View Model, so we add the viewModel parameter:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(viewModel){
Next, our task is to associate the properties of the View Model with the markup that our View "draws". JohnSmith provides the syntax for configuring this link directly in js code. For our case, it will look like this:
var GreeterView = function(){ this.template = "<p>Enter your name: <input type='text'/></p>" + "<p class='message'></p>"; this.init = function(viewModel){ this.bind(viewModel.userName).to("input");
Now everything is ready and we only need to draw our view (it is assumed that there is an element on the page with id = 'greeter'):
js.renderView(GreeterView, new GreeterViewModel()).to("#greeter");
So, on this our mini-application is finished, the result can be seen
here . This example demonstrates the basic philosophy of the framework, but in order to learn more about the capabilities of JohnSmith, let's clarify some details.
Binding
The basis of binding in JohnSmith is observable objects (as in knockout). These objects are created using one of the following methods:
js.bindableValue
- a normal observable object;js.dependentValue
- value depending on other objects;js.bindableList
- observable collection, notifies subscribers about adding / removing items.
Directly binding object A and listener B is configured by the code of the form:
js.bind(A).to(B);
For example:
var firstName = js.bindableValue();
Inside the view, the binding code changes slightly:
And in this case, the search by
.firstName
selector will work only inside the markup of this View, and not in the whole document. This ensures complete independence of the form from the external environment.
The
js.bind(A).to(B)
syntax allows you to combine the “declarative” style with the imperative one and use the jQuery-style in those cases where it is necessary:
If you pass a normal (non-observable) value as a bindable object, then a one-time synchronization with the interface will occur. This allows you to consistently process both observable and “regular” View Model fields:
var ViewModel = function(){ this.firstName = "John";
For rendering complex objects, a child View can be used:
var ViewModel = function(){ this.myAddress = js.bindableValue(); this.initState = function(){ this.myAddress.setValue({ country: 'Russia', city: js.bindableValue(); }); }; };
Views Composition
The view in JohnSmith is the atomic unit for building the interface. Each species is completely independent and provides reusability. The interface of the entire application is made up of individual Species, by building a "tree" (
composite pattern ). That is, there is one main view, it has child views, each of the children has its own child views, etc. Composition is achieved in several ways:
- direct addition of a child view:
var ParentView = function(){ this.init = function(){ this.addChild(".destination", new ChildView(), new ChildViewModel());
- using the View to draw bindable values:
var ParentView = function(){ this.init = function(viewModel){ this.bind(viewModel.details).to(".details", DetailsView);
As a small demonstration of the composite view, the file tree is:

Conclusion
As a conclusion, we denote the features of JohnSmith:
- Companability UI makes it easy to use JohnSmith for projects of any size. With increasing complexity, it is easy to keep the code under control. This is achieved by modularity and a clear division of responsibility between the Species and the Model;
- JohnSmith is very simple - just two basic concepts (View and Bindable), and those are well known to any programmer who worked with the UI. No paradigm shift and no magic behind the scenes;
- JohnSmith operates with ordinary objects with ordinary fields and methods. This means that you will not have to perform any actions relying on string identifiers (such as
model.set('firstName', 'John')
). This approach provides a close friendship with the IDE and goes well with tools like TypeScript or ScriptSharp ; - JohnSmith manipulates DOM elements from JavaScript code, so it needs jQuery.
repository on githubThat's all, thank you for your attention, waiting for constructive criticism!