#How to make Hierarchical GUI Components and Applications (3/3)
Original Posts
http://jinjor-labo.hatenablog.com/entry/2013/12/08/221003
http://jinjor-labo.hatenablog.com/entry/2013/12/10/094825
http://jinjor-labo.hatenablog.com/entry/2013/12/12/225955
This post is after following posts.
http://jinjor-labo.hatenablog.com/entry/2013/12/08/221003 http://jinjor-labo.hatenablog.com/entry/2013/12/10/094825
Previous post shows various implementations for a simple case. Provided that there are two components for each:
- shows members and allows to add new person
- shows the sum amount of their money where a member (and his money) is represented by a number.
, we want to share the model (or otherwise we have to write the code for synchronizing two models). The two views are the observer of the model and they rerender themselves when it updates.
If you write observers, Backbone.js works well. But Angular's directive does better if you want clever HTML template.
<div ng-app="app">
<div ng-controller="Ctrl">
<list values="members"></list>
<sum values="members"></sum>
</div>
</div>
This post will introduce some technics for implementing more components and constructing the whole web application.
Making more complex components, you will face with a problem. Recalculation cost increase when View is far from Model.
To show this problem more clearly, we extend the previous specifications.
- each
ListView
shows the ratio of its model
Then the <list>
directive may be implemented like
var add = function(a, b) {return a + b};
var sumOf = function(values){
return values.reduce(add, 0);
}
angular.module('app', [])
.directive('list', function() {
return {
restrict: 'E',
template: '<div><button ng-click="add()">add</button>\
<ul><li ng-repeat="v in values">{{v}} ({{ratioOf(v)}}%)</li></ul></div>',
scope: { values: '=' },
link: function($scope) {
$scope.add = function(){
var value = $scope.values.length + 1;
$scope.values.push(value);
},
$scope.ratioOf = function(value){
// sumOfが何度も呼ばれてしまう
return (value / sumOf($scope.values) * 100).toFixed(1);
}
}
};
})
.directive('sum', function() { ... })
.controller('Ctrl', function($scope) {
$scope.members = [1, 2, 3];
});
.
It clarify the problems that sumOf
is called too many times while it needs to be calculated only once.
(Some languages like Haskell would memorize the fixed value, but this is just a JavaScript code!)
This problem can be solved by a little bit ugly way.
var sum;
$scope.$watch('values.length', function() {
sum = sumOf($scope.values);
});
$scope.ratioOf = function(value){
return (value / sum * 100).toFixed(1);
}
Demo(JSFiddle): http://jsfiddle.net/jinjor/WR5Pd/6/
In this code, we tell Angular that "Update sum
when values.length
is changed".
Some frameworks like Ember.js and Backbone.js have simpilar features using observers.
What I described ugly is the point that you cannot write it declaratively. In more complecated cases that contain more objects between Model and View, thinking about all dependency of those may be much harder work.
Fortunately, problems like such cases has been studied in 'Reactive Programming', which enables us to write more elegant code that listens values' change and automatically updates their dependant values. The MVC pattern simply divides the consciousness between Models and Views, but in practical cases there are more charactors like View Model and Styles among them. The concepts of Reactive Programming may help us for considering those dependencies.
While Reactive Programming is a generic concept, some languages like Elm tries to apply it to GUI or Games. To know how elegant the code will be, just follow the link and see samples.
Let's go back to the concept of hierarchical components. Now we can share Models among child Views. We can easily extend the concept for whole application like below.
Not all objects are generated in the global scope, but some are generated at view scope to treate their own consciousness. They are invisible of parent and shared by children. This concepts of visiblity is not named yet, but I think they will have some names when this kind of architecture would be more sophisticated.
If you make a much larger application, some troubles might happen.
- Two components are located too far from each other, but they must share a model.
It's not a very rare case. For example, a menu bar or status bar is to be shared by many views.
In this case, it is boring to pass arguments from parent to children for each view between the two. Now consider the case you deside to make a special global object and allow views to access it.
I don't say it is the best answer (and still wondering how it should be solved). Of cource this kind of short cut will make the system tightly-coupled. If you do like this, be careful to treat the implicit dependency and make it belongs to the right scope(e.g. Application Scope, Utility Scope).
Then, how can you write using the above concepts?
An answer is what AngularJS does, but it is just a framework. A more standardized way is Web Components which is specified by W3C. It enable us to define custom HTML tags, which is described by a template and a script. Follow the link and see the examples.
While Web Copmonents is not an MVC framework, you cannot expect some features of data-binding like AngularJS. Web Components simply provides the DOM expansion. The template that contains data-binding is called MDV(Model Driven Views). Some library that support Web Components have MDV too. That points of view is often important when you choose a framework.
I tried to tell my opinion of the concept 'Hierarchical GUI Components', the background and goal. There are also unresolved problems but they will soon be resolved by good engineers. As for me, implementing these concepts is a hard work. The next thing to do is sharing the mental model of that, which may also be difficult because there have been few samples. So, the purpose of this article is shareing my thoughts :)
The final section refers to Web Components. This is an interesting spec itself without MDV, and I'm going to use it agressively in production code.