Bye bye jQuery
After being part of our product since the beginning, it is now the right time to say goodbye to jQuery.
jQuery is a JavaScript library which encapsulates a great deal of native JavaScript functionality into an alternative set of functions with a liberal sprinkling of syntactic sugar.
JavaScript and browser standards have evolved significantly over the history of XenForo. At one point it would have been unfathomable to use anything else. In the not too distant past, jQuery was practically essential for even being able to find a specific element with a specific class name, or being able to support the seemingly endless quirks in now-ancient versions of Internet Explorer and others.
It's a sizeable library in itself too weighing in at over 30 KB added to every page load, and how much of that library were we actually using, anyway?
Well, as the developer who personally went through and rewrote nearly 40,000 lines of code, a lot less than you'd think. And of the language features of jQuery we were using, many things are a simple straight swap to a native JavaScript function that, a long time ago, may either have not existed at all, or too new to have garnered enough browser support.
We acknowledge that there will be some pain points for existing developers who have existing code based on jQuery but, in truth, as long as you aren't maintaining anywhere near the 40,000 lines of code we are, it should be a relatively smooth transition. But, if you get completely stuck, you could always re-add jQuery if you wish but, we'd recommend avoiding that if you can. And removing jQuery as a dependency can start now if you're planning on making changes to existing code before XenForo 2.3 is released. We strongly advise against writing new code that directly uses jQuery functionality at this point.
If needed, we can go into a little bit more technical detail down the road about the changes we have made, but here are some highlights.
Note: The next section gets rather into the weeds in terms of development specifics so move on to the next section if this doesn't interest you.
Block scoped variables
While not strictly related to jQuery, it's worth noting that we no longer use
var
to define variables in favour of using
let
and
const
. This makes the scoping of variables clearer and code less error prone and more predictable.
Selecting element(s)
Selecting elements from the DOM is probably the most frequent operation you'll perform in JavaScript and therefore this is a significant change which, while slightly more verbose, makes code much clearer and less error prone.
jQuery / XF 2.2
JavaScript:
var $element = $('.someClassName')
if ($element.length)
{
// you have an object containing one or more elements; you can call various methods which will interact with this element or elements
}
JavaScript / XF 2.3
JavaScript:
const element = document.querySelector('.someClassName')
if (element)
{
// you have an instance of HTMLElement, if more elements exist with the same selector, you have the first element
}
// ... or
const elements = document.querySelectorAll('someClassName')
if (elements.length)
{
// you have a NodeList object containing one or more HTMLElement objects
}
Arrow functions
Again, while not jQuery related, you will now see
arrow functions being utilised as much as possible. As well as being syntactically nicer to use than traditional anonymous functions, they do not create new bindings for keywords such as
this
.
jQuery / XF 2.2
JavaScript:
var self = this
var callback = function (foo)
{
self.doSomething(foo)
}
JavaScript / XF 2.3
JavaScript:
const callback = (foo) =>
{
this.doSomething(foo)
}
// ...or
const callback = (foo) => this.doSomething(foo)
Event handling
Some functionality provided by jQuery was difficult to leave behind, and the majority of those
really useful methods have been rewritten and ported to vanilla JavaScript as additional methods to our
XF
object. Not least of these is jQuery's event management which supports namespaced events and provides a more intuitive way of removing event listeners from an element that doesn't require a reference to the original event callback.
jQuery / XF 2.2
JavaScript:
var $element = $('.someClassName')
$element.on('namespace.click', function (e)
{
e.preventDefault()
$element.off('namespace.click')
});
JavaScript / XF 2.3
JavaScript:
const element = document.querySelector('.someClassName')
if (element)
{
XF.on(element, 'namespace.click', e =>
{
e.preventDefault()
XF.off(element, 'namespace.click')
})
}
AJAX
This is mostly unchanged from XenForo 2.2 because we still have a
XF.ajax()
wrapper to use as a helper method but, behind the scenes, rather than using jQuery's
$.ajax()
method (which is a wrapper around
XMLHttpRequest
) we have migrated over to using the more modern,
Fetch API.
The main thing to be aware of here is that the
Promise methods available from the result of calling
XF.ajax()
are named slightly differently to what they were with jQuery.
jQuery / XF 2.2
JavaScript:
var t = this
XF.ajax('some-url', data, callback)
.always(function ()
{
t.loading = false
})
JavaScript / XF 2.3
JavaScript:
XF.ajax('some-url', data, callback)
.finally(() =>
{
this.loading = false
})
Storing arbitrary data for an element
Some of jQuery's features, while powerful, can sometimes appear inconsistent or ambiguous. One such feature is the
data
method available on jQuery objects. Depending on its usage, it can manifest different behaviors. Consider the following example:
jQuery / XF 2.2
JavaScript:
var $element = $('.someClassName').first() // <span class="someClassName" data-foo="1"></span>
var foo = $element.data('foo') // reads the data-foo attribute from the element in the DOM
var bar = $element.data('bar') // attempts to read the data-bar attribute from the element which doesn't exist, but the internal data store may have a value set
$element.data('bar', [1, 2, 3]) // sets the bar key in the internal data store to an array
$element.data('foo', '100') // the foo entry in the internal data store for this element now returns 100, ignoring the data-foo attribute which remains unchanged in the actual DOM
In XenForo, there remains a necessity to store arbitrary data, especially data that isn't always a string. However, our current approaches are more predictable and consistent:
JavaScript / XF 2.3
JavaScript:
const element = document.querySelector('.someClassName') // <span class="someClassName" data-foo="1"></span>
const foo = element.dataset.foo // reads the data-foo attribute from the DOM
element.dataset.foo = '100' // sets the data-foo attribute in the DOM
XF.DataStore.set(element, 'bar', [1, 2, 3]) // a new XF.DataStore class is introduced for reading / storing arbitrary, non-string data
const bar = XF.DataStore.get(element, 'bar') // returns an array: [1, 2, 3]
Handler targets
We have a special variable which we pass in to all element/event handlers which is
this.$target
currently. Note that this becomes
this.target
in XF 2.3 as conventionally the
$
prefix on variable names typically is used to denote a jQuery object. In XF 2.3
this.target
represents a
HTMLElement
object.
To find children of the target, this is a little more consistent with vanilla JavaScript than it was with jQuery:
jQuery / XF 2.2
JavaScript:
var $child = this.$target.find('.someChild').first() // returns the first child element which matches the someChild class
JavaScript / XF 2.3
JavaScript:
const child = this.target.querySelector('.someChild') // returns the first child element which matches the someChild class; this will be a HTMLElement; note it uses the same querySelector method rather than a separately named method
Migration support
While understandably, some of the custom methods in XF 2.3 will be unavailable to you until release, we would encourage you to start migrating as much code as possible now to use vanilla JavaScript where practical.
If you feel you need support with converting code to vanilla JavaScript we have approximately 40,000 lines of experience between us and we will attempt to reply to queries in the
XenForo development discussions forum where we can.