QuickUI has a simple and elegant system with which a control can render itself into the DOM: a new control invokes property setters on its base classes, each of which can partially fill in placeholder "slots" in the DOM, until the final control is completely populated with elements. This page walks through how that system works, and why it allows for a great deal of flexibility and extensibility.
At a motivating example, consider the task of creating page template for a simple online store selling a variety of products. This store may have a collection of page templates — some general, some specific — for presenting information about the company and its products. Let's examine how these templates and pages could be implemented as controls.
In the case of creating a page template as a control, we could start by subclassing the base Control class, but there's a more specific subclass called Page that offers a few helpful properties like fill() and title(). We can create a trivial derivation of the Page class like this:
When we create an instance of this trivial page class, we end up with a page whose default content is entirely empty. Accordingly, this page has nothing to render when its created:
We can programmatically add content at run-time by setting this page's content() property, but let's enhance our page control so that it knows how to render its own content.
Control classes can perform work when they are instantiated, and the most common work they do is to invoke property setters on their base classes. Let's create a simple page class that sets its own content() property, which it inherits from a base class:
The above definition stashes a simple JavaScript object (i.e., a dictionary) in a member called "inherited" on the SimplePage class' prototype. Whenever SimplePage is instantiated, e.g., by a call to SimplePage.create(), the values in this "inherited" member will be invoked as property setters. Here, the indicated text string will be passed to the control's content() property.
As it turns out, the closest ancestor class of SimplePage that defines a content() property is Control. The base Control implementation of content() has generally the same effect of setting the innerHTML of control's top element via jQuery $.html() function. So a new instance of SimplePage will end up looking like:
At this stage, we're simply populating the DOM in a declarative fashion; we could have acheived the same results by invoking $.html() with the given text. But by defining the user interface of a page class this way, we open the door to easy extensibility.
Let's use this same approach to create a simple page template that includes a heading area and a content area. This time, the default content will set via "inherited" will include two HTML elements: an h1 to hold the heading, and a div to hold the main content.
The last two signficiant lines define two properties called content() and heading(). These use the Control.chain() helper function. This effectively delegates any values passed to or from SiteTemplate's heading() or content() property to the h1 or div, respectively.
The result is a page that now has two slots that can be filled in:
Note that SiteTemplate overrides the base Control.content() property. Any value passed to SiteTemplate's content() setter will go into the div. Effectively SiteTemplate has partially filled itself in: it renders two elements itself, therbey enforcing a certain page structure, then leaves the other details to outside hosts or subclasses of SiteTemplate. This rendering technique is very common in QuickUI controls, and is similar in concept to abstract classes in various programming languages.
Now that we've defined a SiteTemplate, we can create an instance of that template in a variety of ways. One simple way is to use vanilla HTML:
<body data-create-controls="true" data-control="SiteTemplate"> <div data-property="heading">Home Page</div> Welcome to our web site. </body>
All this text will be indexable, and therefore searchable. When a user views the page, an instance of SiteTemplate is created, and the indicated heading and content are passed to the heading() and content() properties. In this way, the page is completely rendered:
We could also create the exact same page above as a subclass of SiteTemplate:
This method makes the page text opaque to search engines, but may be useful in applications — or portions thereof — where searchability is not important.
Our new SiteTemplate control class, which we created by subclassing Page, is itself extensible. We can use this feature to create a more specific template for the Products area of our hypothetical online store. Just as we defined SiteTemplate to partially fill in a Page, we can define a ProductTemplate class to partially fill in a SiteTemplate:
We once again define a content() property that overrides the base (SiteTemplate) implementation. We define a heading() property that overrides the base implementation as well. The resulting ProductTemplate renders like so:
Using the above template, we can create a page in our Products area via HTML…
<body data-control="ProductTemplate"> <div data-property="Heading">Widget<div> This is a general-purpose widget to satisfy any need. </body>
… or in JavaScript:
The result in either case will be a completely rendered product page:
The sample templates and pages are deliberately simplistic, but the same concepts can be used to acheive a user interface of arbitrary complexity.
There are a number of advantages to creating our application this way: