Understanding of a lifecycle is crucial in an MVVM designs, therefore I decided to give it a full attention. Key notes to take:
data-tier
is handling all of the application lifecycle possibilities, so views may be set up before the model or vice versa- there is an ongoing observation of both: the model and the view, all the changes reflected correspondingly
- JavaScript (model) driven flows are synchronous; the DOM (views) originating changes are asynchronous
I'll start with definition of 2 basic processes: view detection and processing and document processing.
data-tier
's view is simply any DOM element that has data-tie
attribute defined (either by HTML definition or by setting its dataset.tie
property).
Henceforth, any mention of the view
term is according to the above statement.
Any time library processes a view, the following steps are taken:
data-tie
attribute is parsed and preprocessed for a performant future use- element is being updated to the relevant value/s, if the corresponding tie/s is/are already defined
- if relevant, change event listener is added (see below more about this)
- view is added to some internal data structure for a performant future use
- if the element is not yet defined, its processing postponed via
whenDefined
- if the element is a document root (iFrame, shadow DOM), its inner document undergoes document processing
Document processing consists of:
- initialization of
MutationObserver
to track the changes:- any add/remove of child nodes (and their nested tree) tracked; they are flattened, checked for being a view and then either tied or untied, according to the type of event
data-tie
attributes are tracked too; the effect of it is either turning an element into a view (tying) or untying or changing the tie - all according to the attribute's value change
- traversal of DOM for detection and processing of the views
When the data-tier
is imported, it immediatelly performs document processing of the current document.
When init phase is done, the library enters event loop 'dream'. It will react on 3 kind of events:
- JavaScript driven changes to ties
- DOM mutations
- View-to-Model change events
Let's detail each of those flows.
When a new tie is added or an existing tie's model is changed, in both cases one thing happens: data-tier
looks up for any relevant views and updates them accordingly.
The lookup is performed on the internal data structures, it is very fast.
This part of the flow is synchronous.
Example. Assuming, that we already have this HTML in the document:
<span class="display-name" data-tie="currentUser:displayName"></span>
the following JS will play right:
const ve = document.querySelector('.display-name');
console.log(ve.textContent);
// empty string
const currentUser = DataTier.ties.create('currentUser', {
displayName: 'Aya Guller'
});
console.log(ve.textContent);
// Aya Guller - adding the tie is updating the views
currentUser.displayName = 'Nava Guller';
console.log(ve.textContent);
// Nava Guller - changes to model are updating the views
Obviously, if there are many elements with the same data-tie
definition - all of them are updated at the same time.
Note: removal of a tie (DataTier.ties.remove('currentUser')
as in our example) will NOT reset the views, they will stay in their last state. In order to reset the view an explicit reset of model is required (currentUserModel.displayName = null
for example). Of course, reassigning the model's reference (currentUserModel = null
) won't do anything either.
As we've seen above, DOM mutations are being observed and any change relevant to data-tier
is being processed.
Addition of the new views to the DOM gets them updated (if the corresponding model is already defined, of course). Removal of the views from the DOM has mostly some internal effect. Updating views' data-tie
definitions gets them updated as well.
This part of the flow is asynchronous.
Example. Assuming, that the following JS has already run:
const nextToGo = DataTier.ties.create('nextToGo', {
type: 'desert',
name: 'Negev'
});
the following JS will play right:
const ve = document.createElement('span');
e.dataset.tie = 'nextToGo:name';
document.body.appendChild(e);
console.log(ve.textContent);
// empty string - we shall wait next microtask to see the changes
await new Promise(r => setTimeout(r, 0));
console.log(ve.textContent);
// Negev - added elements are processed and if detected as view - updated
ve.dataset.tie = 'nextToGo:type'
await new Promise(r => setTimeout(r, 0));
console.log(ve.textContent);
// desert - 'data-tie' attribute mutations observed and processed too
As per API definition, data-tier
will consider a view valid for a bi-directional tying if:
- there is a property
changeEventName
defined on the view and returning a non-empty string - view is one of the prescripted defaults:
input
,select
,textarea
; their default change event name ischange
, unless redefined as per first punkt above
Before continue, it is important to mention here another concept - defaultTieTarget
. Similarly to the change event name, the default tie target resolved as:
- a non-empty empty string returned from the
defaultTieTarget
property, if such one is defined on the view - otherwise - default, which is
textContent
for the vast majority of the elements except:value
forinput
,select
,textarea
checked
forinput
of typecheckbox
src
forimg
,iframe
,source
Having understand this, the rest is really easy.
When view is processed and found valid for bi-directional tying, data-tier
adds a change event listener to the said event.
When the view raises the event, listener gets the value from the default tie target property and sets it onto model.
This part of flow is asynchronous too.
Example. Assuming, that the following HTML is found in the document:
<input class="age" data-tie="currentUser:age">
we can state, that:
- this input element is considered to be a valid
data-tier
's view - it is also perfectly valid for bi-directional tying
- its default tie target property is
value
data-tier
will be listening onchange
event for View-to-Model changes
Now we can play with it like this:
const v = document.querySelector('.age');
const currentUser = DataTier.ties.create('currentUser', {
age: 4
});
console.log(v.value);
// 4
v.value = 5;
console.log(currentUser.age);
// 4 - JavaScript changes of inputs' value are not raising 'change' event natively
v.dispatchEvent(new Event('change'));
await new Promise(r => setTimeout(r, 0));
console.log(currentUser.age);
// 5 - after an async await 'data-tier' will be notified of change and reflect the new value in the model
We've covered here the essentials of the data-tier
library's lifecycle, its interoperation with the hosting application's cycle and different flows as part of an event-driven library's reactions.