#+TITLE: DB.js: a tiny MVVM framework
#+SHORT-TITLE: intro
#+KEYWORDS: MVVM, small
#+DESCRIPTION: tiny MVVM framework
* Introduction
[[https://github.com/chalaev/DB.js][DB.js]] is a tiny (5.8K minimized) MVVM library written by [[http://chalaev.com][Oleg Shalaev]].
* Small is elegant
It also stands for [[https://www.youtube.com/watch?v=H99XlWQ9KsA]["easy", "fast", "flexible", and "reliable"]].
Consider a simple probabilistic problem: there is a machine (or a car, airplane, space ship, software) composed of many parts (or lines of code),
each part having small probability to fail. How does the probability to have no failures in the machine scale with the number of parts?
It decreases exponentially: a device built of 3.000 parts will be more reliable (and easier to fix) than the one built using
30.000 parts, even if parts of the latter have (say, twice) better quality. High quality of every single part is suppressed
by the overall complexity of the device.
* Most frameworks are over-engineered
The size of frameworks at the bottom of this [[https://gist.github.com/Restuta/cda69e50a853aa64912d?permalink_comment_id=2651262][table]] seems unreasonable:
were the creators paid per kilobyte of code?
| Framework | Size (K) |
|----------------------------------+----------|
| [[https://github.com/chalaev/DB.js][DB.js]] | 5.8 |
| Preact 7.2.0 | 16 |
| Inferno 1.2.2 | 48 |
| Vue 2.4.2 | 58.8 |
| [[https://knockoutjs.com/documentation/introduction.html][KnockoutJS]] | 68 |
| React 16.2.0 + React DOM | 97.5 |
| React 0.14.5 + React DOM | 133 |
| React 0.14.5 \+ React DOM \+ Redux | 139 |
| Angular 1.4.5 | 143 |
| [[https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js][Angular 1.5.6]] | 156 |
| Ember 2.2.0 | 435 |
| Ember 1.13.8 | 486 |
| Angular 2 | 566 |
| Angular 2 + Rx | 766 |
With the general probabilistic consideration above one does not have to try every other MVVM in order to conclude that [[https://github.com/chalaev/DB.js][DB.js]] is less
buggy and more versatile than most other MVVMs.
* Open source code essentially becomes closed when it is huge
Being dependent on someone else's /large/ code is bad because
its complexity means that you cannot fix the errors and optimize the code for your needs.
Web developers already have hard time being dependent on creators of Firefox, Safari, and Chrome.
I do not like certain things in Firefox but I realize that I will never have time to fix them.
Fortunately there is no monopoly for now: if Mozilla becomes too awful, people can /easily/ escape to Chrome and vice versa.
But for businesses /relying/ upon React or Angular
it would be hard to switch: they become /dependent/
on Facebook or Google – huge companies with lots of power and unclear intentions.
* DB.js – basic usage
[[https://github.com/chalaev/DB.js][DB.js]] was inspired by [[https://knockoutjs.com/documentation/introduction.html][KnockoutJS]], so their syntax is similar.
** Observables
Observable is an object created by [[src:DB.observable]], for example,
#+BEGIN(src):JS
var spanText=new DB.observable([],{type:'str',value:"ku-ku"});
#+END
creates an observable [[src:spanText]] having string value and sets its value to "ku-ku".
Any observable is a function:
- to obtain its current value, call it without arguments: [[src:spanText()]],
- to update its value, specify an argument: [[src:spanText("bu-bu")]].
** How it works
1. The web browser loads HTML.
2. HTML loads [[https://github.com/chalaev/DB.js][DB.js]].
3. The function [[src:DB.scanHTML()]] is called (inside HTML or from another JS-file). It scans all nodes for the [[https://github.com/chalaev/DB.js][DB.js]]-related properties. For example, if it encounters [[src:]], the text content (more precisely, [[https://www.w3schools.com/jsref/prop_node_textcontent.asp][textContent]]) of this span-node will be bound to the value of the observable [[src:spanText]].
4. Now if we change the value of [[src:spanText]] by calling [[src:spanText("bu-bu")]], the corresponding span element will be automatically changed from the initial value "ku-ku" to "bu-bu".
Note: If you create DOM-elements (DOMs) dynamically /after/ you have already called [[src:DB.scanHTML()]] at startup, call it
again for these new DOMs. With no arguments, [[src:DB.scanHTML()]] scans the entire HTML.
One can limit the scan to certain DOMs and their children: [[src:DB.scanHTML(\[DOM1,DOM2,…\])]].
* Dependencies
An observable may depend on other observables. For example, consider the following definition:
#+BEGIN(src):JS
var husbandWeight=new DB.observable([],{type:'int',value:220}),
wifeWeight=new DB.observable([],{type:'int',value:160}),
coupleWeight=new DB.observable([husbandWeight,wifeWeight],
{type:'int', compute:()=>husbandWeight()+wifeWeight()});
#+END
where [[src:type:'int']] specifies the (integer) value type, and
[[src:value:220]] together with
[[src:value:160]] assigns the initial values.
The first two observables, [[src:husbandWeight]] and [[src:wifeWeight]] do not depend on any other observable.
The third observable, [[src:coupleWeight]] is declared to be dependent on both
[[src:husbandWeight]] and [[src:wifeWeight]].
Conversely, the value of [[src:coupleWeight]] will be re-calculated every time [[src:husbandWeight]] or [[src:wifeWeight]] is updated.
(The function specified in [[src:compute]] field will be used for that.)
With these observables, the following piece of HTML code will always be up to date:
#+BEGIN(src):HTML
Now the husband weights pounds,
and his wife weights pounds,
so their total weight is pounds.
#+END
where [[src:text]] is the /binding/ type connecting observable values to [[https://www.w3schools.com/jsref/prop_node_textcontent.asp][textContent]] of the corresponding span elements.
Suppose that during the dinner the husband gained 2 pounds, and the wife gained one. In order to update the HTML with their new weight, we execute
#+BEGIN(src):JS
husbandWeight(2+husbandWeight()); wifeWeight(1+wifeWeight());
#+END
After that, the value of [[src:coupleWeight]] together with all HTML elements bound to
our observables
[[src:husbandWeight]],
[[src:wifeWeight]], and
[[src:coupleWeight]],
will be automatically updated.
* More bindings
In the above example we saw how [[src:text]] binding works. Other binding names:
#+BEGIN(edist)
- attr:href
- attr:id
- checked
- class
- display
- ev:input
- ev:keydown
- forEach
- onClick
- onKey
- value
- visible
#+END
This list of bindings is complete and sufficient even for most sophisticated web pages.
Due to its size, [[https://github.com/chalaev/DB.js][DB.js]] can be easily extended by whoever wants to use it.
This flexibility is the main advantage of brevity; better performance (faster web pages) is just an extra benefit.
* Demo and tests
Although [[https://github.com/chalaev/DB.js][DB.js]] is small, its code is non-trivial.
The following web pages ensure that it is not damaged during the latest update:
#+BEGIN(edist)
- [[simple]]
- [[foreach]]
- [[write]]
- [[class]]
- [[dependencies]]
- [[sort]]
- [[compute]]
- [[non-observable]]
#+END
These web pages should work properly in modern (3 years old) versions of Firefox, Chrome, and Safari.
* Further development
- Ensure that [[dependencies][DB.everyElement]] works with usual (non-observable) arrays.
- Try using [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get][getters]] and [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set][setters]] when defining observables. Based on this idea: [[src:Object.defineProperty(window, 'myObservable', {get:()=>123})]]