#+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. * Some frameworks are huge 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 becomes closed when the code 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 (who are not 100% compatible with each other). I do not like certain things in Firefox but I realize that I will never have time to fix them. A good thing about browsers is that 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 – gigantic 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) - [[href][attr:href]] - attr:id - checked - [[mclass][class]] - display - ev:input - ev:keydown - forEach - onClick - onKey - value - visible #+END Using [[https://github.com/chalaev/DB.js][DB.js]] for about a year demonstrates that this list of bindings is complete and sufficient even for most sophisticated web pages. Even if this last statement turns out to be incorrect, [[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, a cherry on the cake. * 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})]]