The previous examples showed how to subclass controls through Xml markup.
This example shows how to write a new control using JavaScript.
Jitsu Controls have a full API, but for most control authors only a handful of methods really matter:
getTagName() | Control authors override this to return the HTML tag name the control uses. |
onFirstRender() | This is called just before the control is rendered the first time. |
onSetValue() | Called whenever anyone sets a value of a property on the control. |
renderChildren() | Render the HTML for the content of the control. |
setValue() | Use this to set properties on the control or on other controls. |
getValue() | Use this to get properties on the control or on other controls. |
The control example on this page uses getTagName(), onSetValue() and renderChildren(), and defines one new method of its own: doUpdate().
Each of these methods is defined using #method.
The #method macro is a convenience macro provided by Jitsu. In a control definition, writing:
#method getTagName() { ... }
is shorthand for writing:
MyLister.prototype.getTagName = function MyLister_getTagName() { ... }
Let's look at each of the methods that make up the control:
The first method is getTagName():
#method getTagName() { return 'ol'; }
Jitsu calls the getTagName() method to pick the HTML element tag to use for the control's peer. In this case we return 'ol' - we want the control to generate an ordered list.
The next method is renderChildren:
#method renderChildren(htmlBuilder) { var items = this.getValue(ID_items); if (items != null) { for (var i = 0; i < items.getLength(); i++) { htmlBuilder.add('<li>'); htmlBuilder.add(items.getValue(i)); htmlBuilder.add('</li>'); } } }
The renderChildren() is a system method called by the framework to generate the HTML contents for the control (rendering of the control's peer tag itself is done by the framework's render() method).
In this case, renderChildren calls the htmlBuilder's add method, adding HTML to the output. Each item in the list is output between <li> tags:
Here's doUpdate():
#method doUpdate() { var element = this.getPeer(); if (element != null) { var htmlBuilder = new HtmlBuilder(); this.renderChildren(htmlBuilder); element.innerHTML = htmlBuilder.toString(); } }
The doUpdate() method implemented in this example shows the most primitive way to update the browser's DOM after data value changes - it works by throwing away all of the existing DOM children and generating fresh HTML.
First, the method contructs an HtmlBuilder and calls this.renderChildren() to add the HTML for the children to the HtmlBuilder:
var htmlBuilder = new HtmlBuilder(); this.renderChildren(htmlBuilder);
Then, it obtains the generated HTML as a string and sets it as the innerHTML property of the DOM element. This pushes the fresh HTML out to the browser:
element.innerHTML = htmlBuilder.toString();
This is a sledgehammer approach to updating the list when the children change. The ListControl class in Jitsu has a more sophisticated update method.
onSetValue() is called by the framework whenever anyone calls setValue on the control to set a property value.
This MyLister control overrides onSetValue to:
#method onSetValue(idname, oldVal, newVal, sender) { if (idname == ID_items) { this.doUpdate(); var del = new Delegate(this, this.doUpdate); if (oldVal != null) { oldVal.removeEventHandler(ID_listChanged, del); } if (newVal != null) { newVal.addEventHandler(ID_listChanged, del); } } #call Control.onSetValue(idname, oldVal, newVal, sender); }
In this onSetValue, we first look to see what property is being set - if it is the ID_items list, we call doUpdate() to update the DOM. Then we wire up event listeners to the new list so that any future changes to the list will also update the dOM. The code for this is:
this.doUpdate(); var del = new Delegate(this, this.doUpdate); if (oldVal != null) { oldVal.removeEventHandler(ID_listChanged, del); } if (newVal != null) { newVal.addEventHandler(ID_listChanged, del); }
This code first constructs a "Delegate" referring to the doUpdate method of this control. Then it tells oldVal to stop forwarding listChanged events to doUpdate(), and newVal to start forwarding listChanged events to doUpdate().
A Delegate is a way of obtaining a reference to a method of a particular object - delegates are constructed from a JavaScript object together with a function that should be called on that object. In this case, the delegate constructor looks like:
var del = new Delegate(this, this.doUpdate);
del is now a reference to this control's doUpdate() method. If someone calls del.invoke(), it in turn calls this object's doUpdate().
Delegates are used to in most places in Jitsu where you want to pass a reference to a method from one object to another.
Through these lines, whenever a ID_listChanged event occurs on the ID_items list, the list will in turn call our doUpdate() method, making the DOM update dynamically.
The last line of onSetValue is very important:
#call Control.onSetValue(idname, oldVal, newVal, sender);
This call's the base class's implementation of onSetValue, to handle changes to other properties on the control. Whenever you override an onXXX method you should almost always call the base class version of the method from your method.
#call is a convenience macro provided by Jitsu to make it easier in JavaScript to call a base class's implementation of a method. Writing:
#call Control.onSetValue(idname, oldVal, newVal, sender);
is shorthand for writing:
Control.prototype.onSetValue.call(this, idname, oldVal, newVal, sender);