Data bindings and Page data - tremho/thunderbolt-common GitHub Wiki

Thunderbolt features a comprehensive Model/View/Controller style architecture. UI elements are updated 'automagically' in response to changes of the model. In ideal principle, all data belongs in the model, and the view of that data is represented by the UI as the data changes. All the app (the controller) needs to do once the bindings have been set up is manipulate the data in response to changes as the app goes about its business (aka 'business logic').

More precisely, Thunderbolt implements something closer to an "MVP (Model/View/Presenter)" paradigm because the UI itself is separated by different host UI technologies (Electron/HTML vs. Nativescript), the "View" implementation is dependent upon the semantic framing of the "Presenter" portions of the "Controller" (as seen most notably within the AppCore, ComCommon, and similar classes).

Model data

Data in the app may be contained within the app "Model". The app model object may be accessed with appCore.model.
It has the functions setAtPath(path, value) and getAtPath(path) where path is a notation describing an entry in the model. For example, 'SomeSection.name' defines a value assigned to 'name' within 'SomeSection'. A section may be created with model.addSection(sectionName, dataObject) where dataObject is a object with all the properties and values for that section. Additional properties can be added to a section with setAtPath(path, value, true), with the last boolean argument being used to force a new entry (or if the type of the property changes). Without this force parameter, an exception is thrown for an unknown or mismatched property set.

Binding declarations

A component declaration in page markup defines a binding for its scope with the bind property, as in bind="SomeSection.name". This will bind the value from this location in the model to a locally bound value with the same name (name in this example). Locally bound properties are contained within the component's this.bound object (the bindingContext for {N} components). To bind under a different name, use bind="SomeSection.name as another" which would make the value locally accessible as bound.another (or in markup syntax "$another").

A bound value is updated whenever the model value is changed (via setAtPath).

####### Accessing bound data from the component The component will be updated on an event triggered by a relevant model change. It may then access it's locally bound property value with this.bound.property, or this.b('object.property'). The b function is handy if the bound value is an object and you want to access a property of that object (or through recursively nested objects), as it will avoid throwing an error if the intermediate object(s) is undefined. Of course, a component may also read from the model directly, with this.com.getApp().model.getAtPath('some.path')

Page Data

Page data is set for the page and there is a page reset and then and update so all the children get updated too. The children check for their page and if it was reset, they set text from their props, otherwise, from local bindings.

Page data is set with appCore.setPageData(pageId, dataObject). This will create initial data and/or update existing data and trigger a page reset and update event.

Page data is bound to the bound object (bindingContext) as data. To access a page property value, then, is via bound.data..

Markup shorthand for this is via the $$ prefix, as in text="$$propertyName". The "$$" prefix is separate from the "$" prefix which binds to the bound object properties directly, as in text="$label"

Updates are handled in StdComp For all controls:

            onBeforeUpdate(props, state) {
              const page = cm.getPageComponent()
              const isReset = page && page.isReset()
              this.bound.text = isReset ? props.text : this.bound.text || props.text
              // console.log(this.root.tagName, 'onBeforeUpdate', props, state, this.bound)
            },

Page Data vs. Bindings

Bindings are an efficient means of binding directly to a component. It is more efficient than page data, but it is also much more granular. If you have a lot of information to display, doling it out one model slot per item is very tedious and difficult to manage. Page Data on the other hand allows you to update a data object associated with the page directly. Unlike a binding, which updates only the component affected and only when that particular bound value changes, each page data update will refresh all the components on the page, with every data change. This is not necessarily as time-consuming as it might sound, since the update mechanisms of the UI layers handle this efficiently in their own ways.
However, it's good to be aware of the differences and the tradeoffs when designing the model / view orchestration for a page.