< PreviousChapter 6: Solution140 /* anchestor component */ class AC { create () { cs(this).create(“Foo/Bar/{SC1,SC2}”, {}, {}, new SC(“SC1”), new SC(“SC2”)) } render () { /* render own UI fragment */ let ui = $(` <div> <div class=”SC1”></div> <div class=”SC2”></div> </div> `) /* let sub-dialogs attach to UI fragment */ const provideSocket = (scope, ctx) => { cs(this).socket({ scope: scope, ctx: ctx, plug: function (ui) { $(this).append(ui) }, unplug: function (ui) { $(ui).remove() } }) } provideSocket(“SC1”, $(“.SC1”, ui)) provideSocket(“SC2”, $(“.SC2”, ui)) /* attach our own UI fragment to own ancestor */ cs(this).plug(ui) } [...] }import $ from “jquery” import cs from “componentjs” /* sub component */ class SC { constructor (name) { this.name = name } render () { /* render and attach UI fragment to ancestor */ let ui = $(`<div>${this.name}</div>`) cs(this).plug(ui) } }The resulting DOM fragment will look like:Hierarchical Component Communication141<div> <div class=”SC1”><div>SC1</div></div> <div class=”SC2”><div>SC2</div></div> </div>Notice: the name SC1 and SC2 occur in three distinct ways: once as the names in the Component Tree, once as the CSS classes on their wrapping <div> elements in the Ancestor Component and once as the Component constructor parameter. All three occurrences are fully independent and use the same names just for the logi-cal relationship.Especially, also notice that the Component Tree is not required to have SC1 and SC2 directly as children below AC for the Pluggable User Interface Fragment Sock-ets mechanism to work. Instead, in this example, there are the dummy intermedi-ate Components Foo and Bar. The important point is that the sockets are working hierarchically.6.6.4 Subscribable Information EventsThe classical communication mechanism in all types of User Interfaces is Events. For instance, if a user interacts with the UI in a browser, the DOM emits certain events for the application to react on in corresponding subscriber callbacks. Addi-tionally, even today’s programming paradigm Reactive Programming is primarily about Event based programming. However, instead of directly using the underlying UI Mask Events (DOM events in the case of HTML5 SPAs), HUICA uses Component Events for the communication between Dialogs. These Component Events are emitted on target Components and delivered to subscribers in an asyn-chronous and hierarchical way on the Component Tree.When an Event is raised on a target Component, this hierarchical event delivery is performed in four distinct phases (see Figure 6.10 on page 142): ■Capturing: first, the Event is delivered to all Components on the path from the root Component (inclusive) towards the target Component (exclusive). This allows ancestor Components to “capture” an event before it even hits the target Component. For instance, a Panel can capture a “reset” Event. This phase is inspired by the corresponding phase in DOM event delivery. ■Targeting: second, the Event is delivered to the target Component. This is the primary intended operation, of course. This phase is inspired by the corresponding phase in DOM event delivery. ■Spreading: third, the Event is delivered to all descendant Components of the target Component in depth-first traversal order. This allows descend-ant Components to also react on events of ancestors. For instance, a “reset” Component Events are inspired by DOM Events, but are more powerful and operate on the Com-ponent Tree instead of the DOM. This is key for lots of derived aspects, including headless test-ing, etc.Chapter 6: Solution142Event can be handled by all sub-Dialogs without having to know the par-ticular sub-Dialogs. This phase is an original invention of HUICA and has no corresponding phase in DOM event delivery. ■Bubbling: forth, the Event is delivered (again) to all Components on the path from the target Component (exclusive) to the root Component (in-clusive). This allows ancestor Components to “also see” an event after it was delivered to descendant Components. For instance, a Panel can also recog-nize a “reset” Event. This phase is inspired by the corresponding phase in DOM event delivery.A delivered Event is an object with the following attributes: ■target: Component: the target component the Event is sent to. This exists for information and internal processing purposes only. ■propagation: Boolean: controls whether Event propagation should con-tinue. By default set to true before Event delivery and if once set to false, Figure 6.10: Component Event DeliveryVMCVMCVMCVCCCCNNCMCMVVMMVVMMVMVVMMVVBubbling4Spreading3Targeting21CapturingHierarchical Component Communication143the entire Event delivery is immediately stopped. Usually used in the Cap-turing phase by subscriber callbacks. ■processing: Boolean: controls whether final default Event processing should be performed by the Event publisher. By default set to true, but can be set to false by subscriber callbacks to stop final Event processing. ■dispatched: Boolean: indicates whether Event was dispatched at least once to a subscriber (which has not declined the dispatching). ■decline: Boolean: controls whether Event should be declined. It is set to false before every Event dispatching and can be set to true by a subscriber callback to decline the particular dispatching. ■phase: String: the current phase of Event dispatching: “capturing”, “targeting”, “spreading” or “bubbling”. This allows subscriber call-backs to distinguish the delivery phase within individual calls to the same callback.For publishing an Event to a target Component, the following Component API is provided by the Component System:/* publish an event */ publish(params: { name: string; capturing?: boolean = true; spreading?: boolean = false; bubbling?: boolean = true; completed?: (event: Event) => void = () => {}; args?: any[] = []; }): void; publish( name: string, ...args: any[] ): void;For subscribing to an Event delivery, the following Component API is provided by the Component System:/* subscribe to an event */ subscribe(params: { name: string; func: (event: Event, ...args: any[]) => any; capturing?: boolean = false; spreading?: boolean = false; bubbling?: boolean = true; }): number; subscribe( name: string, func: (event: Event, ...args: any[]) => any ): number; Chapter 6: Solution144/* unsubscribe from an event */ unsubscribe( id: number ): void;Notice the intentional difference in the default values of options capturing, spreading and bubbling between publish() and subscribe(): when publish-ing the expensive spreading phase is disabled by default and when subscribing only the usual bubbling phase is enabled. Additionally, notice that there intention-ally is no targeting option as this phase is mandatory during both publishing and subscribing.A tiny example of the Event mechanism follows:[...] cs(this).subscribe(“foo:bar”, (event, arg) => { console.log(arg) // -> “quux” }) [...] cs(this).publish(“foo:bar”, “quux”) [...]In practice, for bare convenience reasons, one might optionally allow subscrib-ers to also provide result values back to the publisher. For this, one could change the publish() method to have a return type of any[] and add a result(value: any): void method to the Event object for aggregating the result values. The re-sult is a convenient asynchronous mix between Subscribable Information Events and Registerable Function Services.6.6.5 Registerable Function ServicesSubscribable Information Events (see Subsection 6.6.4 on page 141) are asynchro-nously delivered and hence usually have no result value. Sometimes a synchronous hierarchical communication mechanism is required, too. For this Registerable Function Services exist.Instead of directly using regular method calls, HUICA uses Compo-nent Services for the communication between Dialogs. These Compo-nent Services are called on target Components and resolved to regis-tered services in a synchronous and hierarchical way on the Component Tree.When a Service is raised on a target Component, this hierarchical service resolving is performed in four distinct phases, similar to how Events are dispatched (see Subsection 6.6.4 on page 141): ■Capturing: first, the Service is resolved on all Components on the path from the root Component (inclusive) towards the target Component (ex-Services look similar to Events, but they are a synchronous mechanism with result values and hence have a right on their own. Nevertheless, internally the Compo-nent System might use the Event mechanism to implement the Service mechanism.Hierarchical Component Communication145clusive). This allows ancestor Components to “capture” the Service call be-fore it even hits the target Component. ■Targeting: second, the Service is resolved on the target Component. This is the primary intended operation, of course. ■Spreading: third, the Service is resolved on all descendant Components of the target Component in a depth-first traversal order. ■Bubbling: forth, the Service is resolved (again) on all Components on the path from the target Component (exclusive) to the root Component (inclu-sive). This is the resolving phase which is usually used for most scenarios.For calling a Service on a target Component, the following Component API is pro-vided by the Component System:/* call a service */ call(params: { name: string; args: any[]; capturing?: boolean = false; spreading?: boolean = false; bubbling?: boolean = true; }): any; call( name: string, ...args: any[] ): any;For registering a Service at a target Component, the following Component API is provided by the Component System:/* register for a service */ register(params: { name: string; func: (...args: any[]) => any; capturing?: boolean = false; spreading?: boolean = false; bubbling?: boolean = true; }): number; register( name: string, func: (...args: any[]) => any ): number; /* unregister for a service */ unregister( id: number ): void;Notice that by default in both register() and call(), only the bubbling phase is enabled, as this is the most natural use case.Chapter 6: Solution146Figure 6.12: Possible User Interface Decomposition No. 1Field 1Field 2Label 1Label 2Button 2Button 1Status BarTool BarAreaMenu ItemMenu ItemMenu ItemMenu ItemMenu ItemFigure 6.11: User Interface Before DecompositionField 1WindowField 2Label 1Label 2Menu ItemMenu ItemButton 2Button 1Status BarTool BarAreaMenu ItemMenu ItemMenu ItemHierarchical Dialog Composition147A tiny example of the Service mechanism follows, especially showing how to combine the synchronous Service mechanism with underlying asynchronous UI dialogs with the help of a Promise:[...] cs(this).register(“prompt”, (question) => { return new Promise((resolve, reject) => { $(“.confirm”).confirmDialog({ text: question, onConfirm: (value) => resolve(“yes”), onCancel: () => resolve(“no”) }) }) }) [...] cs(this).call(“prompt”, “Do you use HUICA?”).then((value) => { console.log(value) // -> “yes” or “no” })6.7 Hierarchical Dialog CompositionIf you give a hacker a new toy, the first thing he’ll do is take it apart to figure out how it works. — Jamie ZawinskiEvery User Interface inherently is a hierarchical structure, because outer Dialogs contain inner Dialogs, those inner Dialogs, in turn, contain even more inner Dialogs [Denert 1991] [Siederlsleben 2002] [Haft & Ollek 2009]. While this effectively always forms a hierarchy of Dialogs, it is fully ambiguous how this hierarchy of nested Dia-logs actually looks like. The same optical representation of a User Interface can be composed of multiple individual Dialog hierarchies. 6.7.1 Logical DecompositionDecomposing a User Interface into logical Components is a highly crea-tive act. The User Interface in Figure 6.11 on page 146 is an example. It can hierarchically be decomposed into the UI Fragment Tree of Fig-ure 6.12 on page 146 or into the alternative UI Fragment Tree of Fig-ure 6.13 on page 148. Both resulting UI Fragment Trees are valid and reasonable. The first one just splits the UI into more Fragments, while the second one bundles related Fragments more. Both have their pros and cons. The first one might benefit more from Fragment reusability, and the second one might benefit more from simplified Fragment implementation later.In general, the Logical Decomposition is a highly creative process which usu-ally takes the following orthogonal dimensions into account:The Hierarchical Decom-position of a User Inter-face is a highly creative act which has to take four dimensions into account.Chapter 6: Solution148 ■Visual Similarity: According to the User Interface Ontology (see Section 6.3 on page 119), UI Fragments will be either Composites or Widgets, which both are implemented by Components of View, Model, and Controller roles. In the case of Widgets, which by definition are reusable, the UI Frag-ment can be considered “reusable” at the underlying Component level. Hence, it is reasonable to look for reusable UI Fragments. ■Technical Granularity: a UI Fragment can collapse to just the scope of a single native UI mask widget (in contrast to our UI Widgets directly cor-responding to a UI Fragment in the ontology). However, this granularity is certainly too small and not advised, as an implementation of a custom UI mask widget is better implemented at the lower level of the libraries ren-dering UI masks, than at the higher level of the Component System. On the other hand, a UI Fragment spanning large areas of the UI and this way con-taining many dozens of UI mask widgets is also not advised, as this leads to complex Component implementations, too. Hence, it is reasonable to slice UI Fragments in a way they contain a few but not a large amount of underlying UI mask widgets. Figure 6.13: Possible User Interface Decomposition No. 2Field 1Field 2Label 1Label 2Button 2Button 1Status BarTool BarAreaMenu ItemMenu ItemMenu ItemMenu ItemMenu ItemHierarchical Dialog Composition149 ■Separation of Concern: each UI Fragment shows certain data, provides cer-tain interactions and has a distinct logical domain-specific concern. Taking Separation of Concern into account especially leads to a clear modularity at the underlying Component level. Hence, it is reasonable to slice UI Frag-ments according to their distinct domain-specific or technical concern. ■Use-Case Bracket: the domain-specific use cases usually map onto a few UI Fragments. It is unlikely that a single UI Fragment handles an entire Use-Case, but usually, a group of strongly related UI Fragments as a whole does. Hence, it is reasonable to take the Use-Cases into account by treating them as the bracket over a small number of related UI Fragments.6.7.2 Physical RecompositionAccording to the User Interface Ontology (see Section 6.3 on page 119), the Logical Decomposition of a User Interface results in a tree of UI Frag-ments during development time. Each UI Fragment is either a Compos-ite or a Widget. Both Composites and Widgets are then implemented by Components of View, Model, and Controller roles.During run time those Components are then “physically” recomposed into the Component Tree. For this, the Components playing the View role create the visual representation of their corresponding UI Fragment and with the help of Pluggable UI Fragment Sockets (see Subsection 6.6.3 on page 138), those individual UI Frag-ments are finally hierarchically assembled to form the entire User Interface.It should be explicitly noted that this recomposition of the User Interface out of Components in the Component Tree is not a one-time process. Instead, the Com-ponent Tree is continuously in flux in practice, as Dialogs are opened and closed all the time.The dynamic recomposi-tion of the User Interface out of Components in the Component Tree is an of-ten neglected feature of the Component System.Next >