izi UI development

MVVM, IoC/DI, Event Driven Architecture, Composition

izi (as in 'easy') is a client side microframework based on several concepts from MVC, MVP and MVVM patterns.

It has been developed to render obsolete bloated frameworks for different client side platforms.

izi is not good for everything, but if you are creating a desktop or desktop like application in one of the supported technologies, we strongly encourage you to give it a try.

izi supports techniques like (A)TDD and BDD and allows for a high quality client side code.

izi has been created based on the experience of the GUI team in Sabre Airline Solutions and graciously donated to the open source community with the approval of the Sabre Airline Solutions executives

izi attempts to bring an order and repeatable patterns into the UI development. Although there are many concepts in the wild about how to approach client side MVC, not many implementations really satisfied our needs. We tried to select all the best things from different solutions and gather them together.

izi relies on several programming concepts:

  • The heart of izi is ioc/di. We believe you should keep all of the creational aspects of your UI separated. This is the first step to achieve a fully testable UI code.
  • The spirit of izi is in an event driven architecture and composition. We think it's easy to express very complex interactions in a series of small tasks and link them to the interactions. We call these tasks behaviors.
  • The backbone of izi is made of view models. We have evidence that reflecting UI state 1-1 in a model leads to testable and understandable code. Where possible, we use data binding to wire widgets with view models.

IoC/DI

Depending on the implementation, izi relies either on internal ioc container or on a third party ioc solution. In most cases it is possible to adapt izi to any available ioc container or even to use no ioc at all.

View Models

View models are the most important principle when developing with izi. View model has to reflect all the state of the UI like user input, validity state, enabled/disabled state. Once all the UI state is available on the view model, there is no need to couple any kind of client side logic with the actual view code.

In most scenarios it is required to add event based notification about changes made to the model. Depending on the situation view models can fire events upon changes of individual properties or group of the properties.

It should be also possible to register and unregister observers to the view models.

Connecting views with models

To be able to track the changes from the widgets to the models and the other way around, izi relies on data binding. It is possible to have custom code that synchronizes the view and view model state but in most cases the provided data binding solution works perfectly fine.

Depending on the technology, data binding is either available as a fluent interface or as a built-in mechanism (in case of Flex).

bind().textOf(textField).to(model, "userName");

When data binding is declared within the view code, there is no need of exposing the internal widgets outside of the view component. Instead, it is necessary to provide all the required models to the view.

In most scenarios, ioc can be used to inject required view models to the view. This way, the view needs to know about the model but model does not have to be coupled to the view in any way.

Connecting views with behaviors

View models are responsible for reflecting the view state but they can't react to user interactions like clicking buttons etc. In most cases the underlying logic does not depend on the user interaction that initiated it. For instance, the book search logic is the same when initiated by a mouse click or the double tap.

Behaviors are simple controllers that have access to the view models. They use some of the view models to formulate and send appropriate requests to the logic/services layer and update view models with the results .

Calling services and asynchronous processing

In most technologies there is a need to handle some kind of asynchronous interactions. In browser technologies these are server calls or web workers and threads in desktop technologies. Most of the times, asynchronous code quickly becomes hard to read and bloated. In izi we use a synchronous queue to schedule sequential tasks. The API varies based on the technology, however on a high level the concept it to split sequential work into separate functions or objects and schedule them in a queue that guarantees sequential execution of any asynchronous interactions.

In a below demo you can observe the state and control flow in a typical application built with izi. As you type into the search input, the view model state gets updated appropriately through the binding. User action gets translated into a behavior that calls the remote service. As the service returns, it updates the results view model. Change of the results view model triggers view update through another binding.

The pseudo code is based on the JavaScript implementation.

Amazon search
Searching books... Please wait...
Pseudo code
SearchView = {
  searchModel: izi.inject(SearchModel),
  searchBooks: izi.inject(SearchBooksBehavior),

  iziInit: function () {
    izi.bind().valueOf(queryInput).to(searchModel, "query");
    izi.bind().valueOf(searchModel, "disabledButton").to(searchButton, "disabled");
    izi.perform(searchBooks).when(izi.events.click()).on(searchButton);
  }
}
ResultsView = {
  resultsModel: izi.inject(ResultsModel),

  iziInit: function () {
    izi.bind().valueOf(resultsModel, "foundBooks").to(tableRenderer, "items");
  }
}
SearchModel = {
  query: "",
  disabledButton: ,

  setQuery: function (query) {
   this.query = query; 
   this.disabledButton = isEmpty(query);  
  }
}
ResultsModel = {
  foundBooks: [
    
  ]
}
SearchBooksBehavior = {
  searchModel: izi.inject(SearchModel),
  resultsModel: izi.inject(ResultsModel),
  service: izi.inject(SeachBooksService),

  perform: function () {
    var request = searchModel.toRequest();
    service.searchBooks(request);
  },

  result: function (response) {
     resultsModel.setFoundBooks(response);
  }
}
izi does not enforce any specific project structure however, most of the time the following package structure helps in organizing the code:
  • module1
    • behaviors
      • DoSomething.*
      • DoSomethingElse.*
    • models
      • DoSomethingViewModel.*
    • services
      • DoSomethingService.*
    • views
      • DoSomethingView.*
      • SomeWidget.*
    • ModuleBeans.*
    • ModuleStartup.*