Magento 2 Create jQuery UI Widget

Today we are going to build a custom jQuery UI widget in Magento 2. The jQuery widget factory is a part of jQuery UI. Magento 2 has built in widgets it uses across the platform but we also have the option to build our very own bespoke widgets for our own needs which is what this article is all about.

The built in widgets that come bundled with Magento can be located under lib/web/mage/*.js you can read more about them here. They are used across the default interface, for example the product page detailed info uses the tabs widget.

Why use jQuery UI widgets?

So, why bother using them? The factory provides a flexible base for building complex, stateful plug-ins with a consistent API. It lets us define functionality in one location and re-use it in different areas of the website keeping our code clean and maintainable.

It allows us to pass in custom options for each instance thus giving us greater flexibility while maintaining a single source.

Build it once, use many.

Build the widget

The first step is to build the widget. This article will provide a scaffold for building a bespoke widget, it won’t provide any functionality, but will give you a base for creating widgets in the future.

Within your theme create a new file:

app/design/frontend/<VendorName>/<ThemeName>/Magento_Theme/web/js/mywidget.js

Now add the contents to the file and save:

define([
'jquery',
'jquery/ui',
], function ($) {
'use strict';

$.widget('jason.mywidget', {
options: {
optionOne: '',
optionTwo: '',
},

/**
* @private
*/
_create: function () {
console.log('widget created');
console.log(this.element);
console.log(this.options);
},
});

return $.jason.mywidget;
});

To breakdown the above snippet we are first adding jquery and jquery/ui as dependencies. Its important you include both items, when I was putting together this article I originally didn’t include jquery/ui and hit the following error:

$.widget is not a function

This is because the widget was loading before jQuery UI, we need to make sure our widget gets loaded in after jQuery UI. Remember widgets are part of jQuery UI not jQuery directly.

We then scaffold our bespoke widget and return the widget at the end of the file. The private _create method is built into the jQuery UI factory and gets automatically called when a widget is initialised.

Widget names must use the format namespace.widgetname the namespace is usually the company name. You can read about Magento recommended naming conventions here.

Initialise the widget

Now we have the widget code in place, we need to call it where needed. I will show you 3 different methods to do this.

I have previously written an in-depth article on how to initialise JavaScript in Magento so you might want to check that out if you’re not familiar with the concepts.

Method 1

Using the imperative notation directly inside a .phtml file. Magento does not recommend this method but its good to know how it can be achieved:

<div class="selector">
<p>Example jQuery UI Widget</p>
</div>

<script>
require([
'jquery',
'Magento_Theme/js/mywidget'
], function ($) {
'use strict';

$(function () {
$('.selector').mywidget({
optionOne: 'valueOne',
optionTwo: 'valueTwo',
});
});
});
</script>

Note we call mywidget on our selector which is referencing the same name as specified when we created the widget.

You can also initialise the widget with no selector, depending on your functionality you might not need a selector:

<div class="selector">
<p>Example jQuery UI Widget</p>
</div>

<script>
require([
'jquery',
'Magento_Theme/js/mywidget'
], function ($, importedMyWidget) {
'use strict';

$(function () {
importedMyWidget({
optionOne: 'valueOne',
optionTwo: 'valueTwo',
});
});
});
</script>

When calling the widget with no selector we define a new variable in this scope called importedMyWidget which allows us to initialise the widget directly.

Method 2

Using the declarative notation with text/x-magento-init inline script:

<div class="selector">
<p>Example jQuery UI Widget</p>
</div>

<script type="text/x-magento-init">
{
".selector": {
"Magento_Theme/js/mywidget": {
"optionOne": "valueOne",
"optionTwo": "valueTwo"
}
}
}
</script>

Similar to method 1 you can also initialise the widget with no selector by using the asterisk:

<div class="selector">
<p>Example jQuery UI Widget</p>
</div>

<script type="text/x-magento-init">
{
"*": {
"Magento_Theme/js/mywidget": {
"optionOne": "valueOne",
"optionTwo": "valueTwo"
}
}
}
</script>

Method 3

Using the declarative notation with data-mage-init attribute:

<div class="selector" data-mage-init='{
"Magento_Theme/js/mywidget": {
"optionOne": "valueOne",
"optionTwo": "valueTwo"
}
}'>
<p>Example jQuery UI Widget</p>
</div>

This method binds it to the selector the attribute is added to, thus you must have a selector using this method.

Success! All of these methods will initialise our new bespoke widget. Now we have the scaffold in place we can move onto adding its functionality depending on our needs.

Customise the widget

At the moment the widget doesn’t do a whole lot, we are just logging the element and options that get passed through within our widget file:

 /**
* @private
*/
_create: function () {
console.log('widget loaded');
console.log(this.element);
console.log(this.options);
},

When there is no selector this.element returns nothing:

Building out the functionality of the widget is beyond the scope of this article but this does provide you with a scaffold to start from. I would recommend reading the jQuery widget factory documentation for adding in functionality.

Create a new widget by extending an existing widget

When creating your new widget you can also base it off an existing jQuery UI widget. To do this you need to add the widget as a dependency and then pass a second argument to the jQuery widget factory like so:

define([
'jquery',
'jquery/ui',
'mage/dropdown'
], function ($) {
'use strict';

$.widget('jason.mywidget', $.mage.dropdownDialog, {
// add functionality
});

return $.jason.mywidget;
});

In this example we add mage/dropdown as our new dependency and then create a new widget based off the dropdown widget by passing it to the jQuery widget factory.

Alias the widget

To improve things even further we can add an alias for our new widget. This essentially lets us use a shorthand for the widget name.

Magento uses aliases across the platform, for example the mage/dropdown widget has an alias of dropdownDialog.

You can see what other aliases there are by opening up the requirejs-config.js file from pub/static and look at the map config:

pub/static/frontend/<VendorName>/<ThemeName>/<Locale>/requirejs-config.js
map: {
'*': {
'tabs': 'mage/tabs',
'collapsible': 'mage/collapsible',
'dropdownDialog': 'mage/dropdown',
'accordion': 'mage/accordion',
'tooltip': 'mage/tooltip',
}
},

To create our own alias we need to add it to the requirejs-config.js file. Within your theme create a new file or open the existing file:

app/design/frontend/<VendorName>/<ThemeName>/requirejs-config.js

Now add the contents to the file and save:

var config = {
map: {
'*': {
'myNewWidget': 'Magento_Theme/js/mywidget'
}
}
};

Now where we initialised the widget instead of using the full path we can use the alias shorthand:

<div class="selector" data-mage-init='{
"myNewWidget": {
"optionOne": "valueOne",
"optionTwo": "valueTwo"
}
}'>
<p>Example jQuery UI Widget</p>
</div>

That’s a wrap! In my next article I’ll show you how to extend existing jQuery UI widgets to amend the existing functionality.