I work on an application that uses quite a lot of Javascript components based on the YUI framework, which has been obsolete since around 2014. We recently had the budget to start migrating them to plain Javascript.
How YUI widgets work
Our YUI components use the syntax of YUI3. Sometimes they also use widgets from YUI2 : these have to be rebuilt, or migrated to use another library.
A YUI 3 component is structured as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// component declaration YGlobal.add('mycomponent', function (Y) { // object constructor function MyComponent() { } // static method MyComponent.Foo = function() { doSomething(); }; // extends the YUI components Y.extend(MyComponent, Y.Base, { // initializes the component initializer: function(config) { this.name = config.name; }, // destroys the component destructor: function() { }, // instance method foo: function() { } }); // adds the component to the namespace Y.namespace('mycompany').MyComponent = MyComponent; // component version }, '0.1', { // other YUI components requires: ['list', 'of', 'used', 'components'] } ); |
The component is called this way:
1 2 3 4 5 6 7 8 9 |
// the "use" loads the file if it's not already YGlobal.use('mycomponent', function (Y) { // you can now use static methods Y.mycompany.MyComponent.Foo('bar'); // or create an instance let mc = new Y.mycompany.MyComponent(); mc.foo(); }); |
Migration
Migrating the components implies a lot of steps. The first and easiest, is to replace the YUI methods inside with native or equivalent methods. As a side note, we’re using JQuery in the application : much easier than native JS for querying DOM, creating elements, making ajax calls, etc.
The Y.use('foo', function (X) {})
can be removed, and calls to X.foo.bar()
will be replaced with calls to the migrated “foo” component. Warning: existing return
inside the Y.use
were just breaking out of the use
, they now break out of the wrapping method. doc
Y.JSON
becomes just JSON
. doc
Y.log
becomes console.log
, or more often nothing. doc
Y.each(foo, function (bar)
can be replaced with $.each(foo, function (index, bar)
, or just for (let bar in foo)
in native JS ; take care, Y.each
takes a second parameter for the execution context, but not $.each
: calls to this
inside the Y.each
will have to be replaced. doc YUI ; doc Jquery
Y.UA
can be switched to equivalents with UAParser ; that said, try not to use it at all if you can. doc
Y.Cookie
can be remplaced by equivalents using js-cookie (managing cookies in JS is a pain) ; it so happens that get
, set
and remove
methods exist in both librairies, so it’s often just replacing Y.Cookie
with Cookies
. doc
Y.Lang.isValue
is actually pretty useful, and I chose to copy its source. doc ; source
Y.Lang.isArray
is replaced with Array.isArray
.
Y.QueryString
can be replaced with URLSearchParams : const urlParams = new URLSearchParams(location.search);
AJAX calls
Y.io
(doc) is replaced with $.ajax
(doc) which works similarly, but the signature differences are pretty important.
If you need blocking calls, sync: true
becomes async: false
; but if you can, making async calls with a callback is a better idea.
The object parameter { on: { success: function(id, xhr, args) {}, failure: function(id, xhr, args) {}, context: this } }
“goes up a level”, context
is removed, and failure
is renamed error
: { success: function(data, status, xhr) {}, error: function(xhr, status, error) {} }
.
You can replace headers: { 'Content-Type': 'application/json' }
with contentType: 'application/json'
but I think both work.
You can also remplace the success/error methods with promises, for instance Y.io('foo', { success: function() {} })
becomes $.ajax('foo').done(function() { })
.
In the method success
/done
:
- the signature goes from
id, xhr, args
todata, status, xhr
- the
data
object can be used directly, rather than parsingxhr
: if it’s JSON, it’s already been parsed, you need to removeJSON.parse(xhr.responseText)
.
The method error
/fail
also changes signature (from id, xhr
to xhr, status, errorThrown
).
Node operations
The base YUI selector Y.one(foo)
becomes $(foo)
– doc YUI ; doc Jquery
All variable usage will then have to be modified:
- YUI returns
null
if no element is found, whereas JQuery returns an object with a zerolength
property: you’ll have to change your tests on empty values. .appendChild
becomes.append
.one
becomes.find
Y.Node.create('foo')
becomes$('<foo></foo>')
.set('text', x)
becomes.text(x)
.set('innerHTML', x)
becomes.html(x)
Take especially care if the DOM object is exposed by the component : other parts of your application will have to be changed, too.
Final template
Here is the template we’re now using to create JS components/widgets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
mycompany = mycompany || {}; // OPTIONAL : Jquery plugin $.fn.MyComponent = function (options) { // do something with this var instance = new mycompany.MyComponent(options); return this; }; // constructor mycompany.MyComponent = function MyComponent(settings) { var that = this; settings = $.extend({ name: '', nodeId: '' }, settings); that._settings = settings; that._name = settings.name; that._nodeId = settings.nodeId; that.render(); return that; }; // render the control mycompany.MyComponent.prototype.render = function render() { // TODO }; // sample static method mycompany.MyComponent.Foo = function Foo() { // TODO }; |