Improved JS Framework
XF2 itself still uses jQuery as its primary JS framework but has removed its dependency on the aging and unsupported jQuery Tools. We have built our own framework around jQuery which makes instantiation and extension of JS easier.
We have two types of handler; a click handler and an element handler. Each handler type is an object which has similar behaviors and similar functions within that should be extended. Click handlers execute code once the element is clicked, and element handlers execute once the element is initialized. Let's take a look at a basic click handler:
JavaScript:
XF.LinkClick = XF.Click.newHandler({
eventNameSpace: 'XFMyLinkClick',
options: {
alertText: 'Link has been clicked!'
},
init: function()
{
alert('Initialization. This fires on the first click.');
},
click: function(e)
{
e.preventDefault();
alert(this.options.alertText); // Alerts: 'Link has been clicked!'
}
});
XF.Click.register('mylink', 'XF.LinkClick');
To set up an element to call the above code when clicked, you simply add a
data-xf-click
attribute with a value of
mylink
(the identifier, the first argument of the register line).
HTML:
<a href="javascript:" data-xf-click="mylink">Click here!</a>
Some interesting things to note:
- We specify a unique event namespace which means that a number of events will be fired when the link is clicked. Specifically, they are before the handler is initialized, after the handler is initialized, before the click code executes and after the click code executes.
- We have an options object. Options are passed to the handler automatically and allow an element to adjust its configuration based on
data-X
attributes passed in. For example, when the above link is clicked, the alert "Link has been clicked!" will display. If we wanted this to alert something different, we can do that by adjusting the HTML as follows:
HTML:
<a href="javascript:" data-xf-click="mylink" data-alert-text="Something different!">Click here!</a>
Element handlers work in a nearly identical way, but the
init
function for element handlers executes when the element is activated (usually on page load for the entire document, or on specific elements on demand).
It is also trivially possible to extend an existing handler. Let's take a look at a very basic element handler which when assigned to an element will change its text color to red, and underlined:
JavaScript:
XF.RedText = XF.Element.newHandler({
options: {
color: 'red',
decoration: 'underline'
},
init: function()
{
this.changeColor();
this.underline();
this.alert();
},
changeColor: function()
{
this.$target.css({
color: this.options.color
});
},
underline: function()
{
this.$target.css({
'text-decoration': this.options.decoration
});
},
alert: function()
{
alert('The text changed to red and underline...');
}
});
XF.Element.register('red-text', 'XF.RedText');
HTML:
<div data-xf-init="red-text">This is black text.</div>
When this element initializes, the "This is black text" text will turn red and become underlined and an alert 'The text changed to red and underline...' will be displayed on the screen.
We might want to re-use or extend this code for a different element handler, and this is possible using
XF.extend
. This allows you to extend an existing handler with your own code. You can then change default options, or even override or extend entire functions with the added option of still calling the original function if desired. Let's see that in action:
JavaScript:
XF.BlueText = XF.extend(XF.RedText, {
__backup: {
'alert': '_alert'
},
options: $.extend({}, XF.RedText.prototype.options, {
color: 'blue',
decoration: 'line-through'
}),
alert: function()
{
this._alert();
alert('...actually, it changed to blue and strike through!');
}
});
XF.Element.register('blue-text', 'XF.BlueText');
HTML:
<div data-xf-init="blue-text">This is black text.</div>
What we have done here is we have registered a new element handler called
blue-text
but the bulk of the behavior is coming from the
red-text
handler.
Notice that the default options of the
red-text
handler are overridden so that the text is going to be blue and line-through rather than red and underline.
We've also used a
__backup
object to map the
alert
function in the original handler to a new name of
_alert
in the new handler. This is equivalent to extending a class and method in PHP and calling the
parent::
method.
The end result of this blue-text handler is that the color of the text is changed to blue and underline (based on our extended options), the alert text from the original handler is displayed ('The text changed to red and underline...'), followed by the new alert text we've specified in the new handler ('...actually, it changed to blue and strike through!').
While this demonstrates a clear hierarchy style, we have also exposed a more dynamic method of extension. This would likely fall into the realm of "monkey patching".
JavaScript:
XF.Element.extend('red-text', {
alert: function()
{
alert('Hey a different message');
}
});
This dynamically extends the
XF.RedText
instance in place and overrides the functionality of the alert function. All code that is using the
red-text
element will automatically use the extended version. Further, multiple extensions can happen to the same element handler without causing name conflicts; in this regard, it's similar to the class extension system for PHP.
Additionally, this
XF.Element.extend
method can be called before or after the base element is registered. This is relevant when JS is loaded asynchronously. (Clearly, it needs to be called before the element is instantiated, but the JS framework won't do that until all pending asynchronous JS has loaded.)