Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

User:Mark Johnson/Javascript: Difference between revisions

From MoodleDocs
m (Text replacement - "<code (.*)>" to "<syntaxhighlight lang="$1">")
 
(6 intermediate revisions by 3 users not shown)
Line 11: Line 11:
==== Enabling jQuery ====
==== Enabling jQuery ====
This was simple - the jQuery library was downloaded and saved in the block's directory.  The following line then included the library:
This was simple - the jQuery library was downloaded and saved in the block's directory.  The following line then included the library:
  <code php>$this->page->requires->js('/blocks/quickcourselist/jquery.js');</code>
  <syntaxhighlight lang="php">$this->page->requires->js('/blocks/quickcourselist/jquery.js');</syntaxhighlight>
jQuery/$ then became available as a global Javascript function.
jQuery/$ then became available as a global Javascript function.


Line 18: Line 18:


YUI:
YUI:
<code javascript>    init: function(Y, instanceid) {
<syntaxhighlight lang="javascript">    init: function(Y, instanceid) {
         this.Y = Y;
         this.Y = Y;
         this.instanceid = instanceid;
         this.instanceid = instanceid;
Line 27: Line 27:
         Y.one('#quickcourselistsearch').on('keyup', function(e) {
         Y.one('#quickcourselistsearch').on('keyup', function(e) {
             var searchstring = e.target.get('value');
             var searchstring = e.target.get('value');
             M.block_quickcourselist.search(searchstring);
             this.search(searchstring);
         });
         }, this);
         Y.one('#quickcourseform').('submit', function(e) {
         Y.one('#quickcourseform').on('submit', function(e) {
             e.preventDefault();
             e.preventDefault();
             var searchstring = e.target.getById('quickcourselistsearch').get('value');
             var searchstring = e.target.getById('quickcourselistsearch').get('value');
             M.block_quickcourselist.search(searchstring);
             this.search(searchstring);
         });
         }, this);
     }</code>
     }</syntaxhighlight>


jQuery:
jQuery:
<code javascript>    init: function(instanceid) {
<syntaxhighlight lang="javascript">    init: function(instanceid) {
         this.instanceid = instanceid;
         this.instanceid = instanceid;


Line 52: Line 52:
             M.block_quickcourselist.search(searchstring);
             M.block_quickcourselist.search(searchstring);
         });
         });
     }</code>
     }</syntaxhighlight>
The jQuery code is marginally simpler for this function.  YUI passes an "event façade" object to the event handler rather than the raw DOM event, meaning that an additional method call is requires to get the search box's value.  However, it also allows the search box to be pinpointed within the form when handling the submit event, rather than having to search the entire document, which is probably a bit quicker.
The jQuery code is marginally simpler for this function.  YUI passes an "event façade" object to the event handler rather than the raw DOM event, meaning that an additional method call is requires to get the search box's value.  However, it also allows the search box to be pinpointed within the form when handling the submit event, rather than having to search the entire document, which is probably a bit quicker.
The other noticeable difference is that each event to be handled has its own method for attaching a listener, rather than each event using the same handler.  This has it's pros and cons - jQuery's way is tidier if you're adding a single listener to an element, or want to add different handlers to different events for the same element by chaining the method callsHowever YUI's method is tidier if you're adding the same handler for several events on the same element, as you can pass an array of events to listen for.
In this example, jQuery uses a specific method for attaching the keyup and submit event handlers.  This is slightly tidier if wishing to attach a single handler, or wishing to chain method calls to attach different handlers for different events.  The <tt>.bind()</tt> method is also available to attach a handler to multiple events, as you can with <tt>Y.on()</tt>.
Finally, as Tim Hunt pointed it, it's also possible to pass an object to <tt>Y.on()</tt> to use as the context for the callback, as shown here by the use of <tt>this</tt>.


==== AJAX ====
==== AJAX ====
YUI:
YUI:
<code javascript>search: function(string) {
<syntaxhighlight lang="javascript">search: function(string) {


         var Y = this.Y;
         var Y = this.Y;
Line 68: Line 69:
         this.xhr = Y.io(uri, {
         this.xhr = Y.io(uri, {
             data: 'course='+string+'&instanceid='+this.instanceid,
             data: 'course='+string+'&instanceid='+this.instanceid,
            context: this,
             on: {
             on: {
                 success: function(id, o) {
                 success: function(id, o) {
Line 77: Line 79:
             }
             }
         });
         });
     }</code>
     }</syntaxhighlight>


jQuery:
jQuery:
<code javascript>search: function(string) {
<syntaxhighlight lang="javascript">search: function(string) {


         uri = M.cfg.wwwroot+'/blocks/quickcourselist/quickcourse.php';
         uri = M.cfg.wwwroot+'/blocks/quickcourselist/quickcourse.php';
Line 97: Line 99:
             }
             }
         });
         });
     }</code>
     }</syntaxhighlight>
These two examples are very similar indeed.  Two notable things which makes jQuery slightly nicer: the options for the AJAX call allow you to specify a context which the callbacks will be called in.  This means that you can use <tt>this</tt> in the callbacks and have it refer to your Javascript module, rather than the XHR as would otherwise be the case.  As far as I'm aware this isn't possible in YUI.  Also, jQuery also parses the JSON in the responseText automatically on successs, returning an object rather than a string as the first argument.
These two examples are very similar indeed.  The only notable thing that's nicer with jQuery is that it parses the JSON in the responseText automatically on successs, returning an object rather than a string as the first argument.
 
::''YUI does let you set the context for event handlers: http://yuilibrary.com/yui/docs/api/classes/Node.html#method_on''--[[User:Tim Hunt|Tim Hunt]] 17:34, 2 November 2011 (WST)
::''Thanks Tim, I've amended the Y.on example above.  Further investigation shows the context specification in Y.io to be possible too: http://yuilibrary.com/yui/docs/io/#the-configuration-object'' --[[User:Mark Johnson|Mark Johnson]] 16:47, 23 November 2011 (WST)


==== DOM manipulation ====
==== DOM manipulation ====
YUI:
YUI:
<code javascript>success: function(id, o) {
<syntaxhighlight lang="javascript">success: function(id, o) {
                     var courses = Y.JSON.parse(o.responseText);
                     var courses = Y.JSON.parse(o.responseText);
                     var block = M.block_quickcourselist;
                     var block = M.block_quickcourselist;
Line 114: Line 119:
                     list.setAttribute('id', 'quickcourselist');
                     list.setAttribute('id', 'quickcourselist');
                     block.progress.setStyle('visibility', 'hidden');
                     block.progress.setStyle('visibility', 'hidden');
                 },</code>
                 },</syntaxhighlight>


jQuery:
jQuery:
<code javascript>success: function(courses, status) {
<syntaxhighlight lang="javascript">success: function(courses, status) {
                 list = $('<ul />');
                 list = $('<ul />');
                 if (courses.length > 0) {
                 if (courses.length > 0) {
Line 127: Line 132:
                 list.attr('id', 'quickcourselist');
                 list.attr('id', 'quickcourselist');
                 this.progress.css('visibility', 'hidden');
                 this.progress.css('visibility', 'hidden');
             }</code>
             }</syntaxhighlight>
There's really nothing to call between these two examples, except perhaps the use of $() being slightly more concise than Y.Node.create().
There's really nothing to call between these two examples, except perhaps the use of $() being slightly more concise than Y.Node.create().


Line 137: Line 142:
==== Comments ====
==== Comments ====
As an additional comment, I have had some experience with the Prototype framework and find that Prototype would also be tidier in places than either YUI or jQuery.  For example, Prototype implements each() as a method of the Array object, so the example above could use  
As an additional comment, I have had some experience with the Prototype framework and find that Prototype would also be tidier in places than either YUI or jQuery.  For example, Prototype implements each() as a method of the Array object, so the example above could use  
<code javascript>courses.each(fn)</code>
<syntaxhighlight lang="javascript">courses.each(fn)</syntaxhighlight>
in place of  
in place of  
<code javascript>Y.Array.each(courses, fn)</code> or <code javascript>$.each(courses, fn)</code>
<syntaxhighlight lang="javascript">Y.Array.each(courses, fn)</syntaxhighlight> or <syntaxhighlight lang="javascript">$.each(courses, fn)</syntaxhighlight>
which is, IMHO, a lot nicer.  This isn't a suggestion that we should switch to Prototype, but merely a demonstration that each framework has it's relative merits, and which is "best" is probably largely subjective.
which is, IMHO, a lot nicer.  This isn't a suggestion that we should switch to Prototype, but merely a demonstration that each framework has it's relative merits, and which is "best" is probably largely subjective.


[[User:Mark Johnson|Mark Johnson]] 16:56, 2 November 2011 (WST)
[[User:Mark Johnson|Mark Johnson]] 16:56, 2 November 2011 (WST)

Latest revision as of 08:09, 15 July 2021

This page follows on from Frank Ralf's work at examining jQuery vs YUI2, this time looking at YUI3. The idea is to establish what the relative advantages/disadvantages/differences are between the two, and whether there's any compelling reason to switch Moodle's Javascript framework from YUI3 to jQuery.

Experiment 1

The experiment shown below is a conversion of my Quickcourselist block from YUI to jQuery. The block allows a user to type all or part of a course's shortname or fullname, and presents a list of links to matching courses. The block can work without Javascript by displaying a submit button, but is progressively enhanced to allow instant searching via AJAX.

To achieve this, the javascript must attach event listeners, perform ajax requests, and perform some DOM manipulation. I've recently become aware that the effect could possibly be achieved more easily using YUI's AutoComplete widget. It would be interesting to see this solution and how it would compare to the jQuery/jQuery UI equivalent.

All code for the block is available on Github. The original code can be found in the "dev" branch - the converted code can be found in the "jq" branch. I am an expert in neither, so any improvements to either version are welcome (please use this page's discussion).

Enabling jQuery

This was simple - the jQuery library was downloaded and saved in the block's directory. The following line then included the library:

$this->page->requires->js('/blocks/quickcourselist/jquery.js');

jQuery/$ then became available as a global Javascript function.

init function

The init function finds the progress indicator so it can be shown/hidden later, and attaches the event handlers to call the search function. We attach a handler for when a character is typed, and one for when the form is submitted (the submit button is hidden when Javascript is on, but a user may still press Enter).

YUI:

    init: function(Y, instanceid) {
        this.Y = Y;
        this.instanceid = instanceid;

        this.progress = Y.one('#quickcourseprogress');
        this.xhr = null;

        Y.one('#quickcourselistsearch').on('keyup', function(e) {
            var searchstring = e.target.get('value');
            this.search(searchstring);
        }, this);
        Y.one('#quickcourseform').on('submit', function(e) {
            e.preventDefault();
            var searchstring = e.target.getById('quickcourselistsearch').get('value');
            this.search(searchstring);
        }, this);
    }

jQuery:

    init: function(instanceid) {
        this.instanceid = instanceid;

        this.progress = $('#quickcourseprogress');
        this.xhr = null;

        $('#quickcourselistsearch').keyup(function(e) {
            var searchstring = e.target.value;
            M.block_quickcourselist.search(searchstring);
        });
        $('#quickcourseform').submit(function(e) {
            e.preventDefault();
            var searchstring = $('#quickcourselistsearch').get('value');
            M.block_quickcourselist.search(searchstring);
        });
    }

The jQuery code is marginally simpler for this function. YUI passes an "event façade" object to the event handler rather than the raw DOM event, meaning that an additional method call is requires to get the search box's value. However, it also allows the search box to be pinpointed within the form when handling the submit event, rather than having to search the entire document, which is probably a bit quicker. In this example, jQuery uses a specific method for attaching the keyup and submit event handlers. This is slightly tidier if wishing to attach a single handler, or wishing to chain method calls to attach different handlers for different events. The .bind() method is also available to attach a handler to multiple events, as you can with Y.on(). Finally, as Tim Hunt pointed it, it's also possible to pass an object to Y.on() to use as the context for the callback, as shown here by the use of this.

AJAX

YUI:

search: function(string) {

        var Y = this.Y;
        uri = M.cfg.wwwroot+'/blocks/quickcourselist/quickcourse.php';
        if (this.xhr != null) {
            this.xhr.abort();
        }
        this.progress.setStyle('visibility', 'visible');
        this.xhr = Y.io(uri, {
            data: 'course='+string+'&instanceid='+this.instanceid,
            context: this,
            on: {
                success: function(id, o) {
                    ...
                },
                failure: function(id, o) {
                    ...
                }
            }
        });
    }

jQuery:

search: function(string) {

        uri = M.cfg.wwwroot+'/blocks/quickcourselist/quickcourse.php';
        if (this.xhr != null) {
            this.xhr.abort();
        }
        this.progress.css('visibility', 'visible');
        this.xhr = $.ajax(uri, {
            data: 'course='+string+'&instanceid='+this.instanceid,
            context: M.block_quickcourselist,
            success: function(courses, status) {
                ...
            },
            error: function(o, status) {
                ...
            }
        });
    }

These two examples are very similar indeed. The only notable thing that's nicer with jQuery is that it parses the JSON in the responseText automatically on successs, returning an object rather than a string as the first argument.

YUI does let you set the context for event handlers: http://yuilibrary.com/yui/docs/api/classes/Node.html#method_on--Tim Hunt 17:34, 2 November 2011 (WST)
Thanks Tim, I've amended the Y.on example above. Further investigation shows the context specification in Y.io to be possible too: http://yuilibrary.com/yui/docs/io/#the-configuration-object --Mark Johnson 16:47, 23 November 2011 (WST)

DOM manipulation

YUI:

success: function(id, o) {
                    var courses = Y.JSON.parse(o.responseText);
                    var block = M.block_quickcourselist;
                    list = Y.Node.create('<ul />');
                    if (courses.length > 0) {
                        Y.Array.each(courses, function(course) {
                            Y.Node.create('<li><a href="'+M.cfg.wwwroot+'/course/view.php?id='+course.id+'">'+course.shortname+' '+course.fullname+'</a></li>').appendTo(list);
                        });
                    }
                    Y.one('#quickcourselist').replace(list);
                    list.setAttribute('id', 'quickcourselist');
                    block.progress.setStyle('visibility', 'hidden');
                },

jQuery:

success: function(courses, status) {
                list = $('<ul />');
                if (courses.length > 0) {
                    $.each(courses, function(key, course) {
                        $('<li><a href="'+M.cfg.wwwroot+'/course/view.php?id='+course.id+'">'+course.shortname+' '+course.fullname+'</a></li>').appendTo(list);
                    });
                }
                $('#quickcourselist').replaceWith(list);
                list.attr('id', 'quickcourselist');
                this.progress.css('visibility', 'hidden');
            }

There's really nothing to call between these two examples, except perhaps the use of $() being slightly more concise than Y.Node.create().

Conclusions

This simple example shows that jQuery can be slightly cleaner or more convenient to write than YUI3. However, in terms of functionality, at this level there is nothing to call between them.

As a relative jQuery newbie, while performing this experiment I drew upon the jQuery - YUI3 rosetta stone and jQuery API reference. The two frameworks are structured very differently, YUI being fiercely modular and keeping each module's methods in seperate namespaces, while jQuery's core functionality is all made available as methods of the jQuery/$ global. jQuery has a lot of plugins available to extend and enhance its functionality. Further experiments should look at more complex examples to see if the functionality required by Moodle would require a reliance on 3rd party jQuery plugins, and if this option is sustainable.

Comments

As an additional comment, I have had some experience with the Prototype framework and find that Prototype would also be tidier in places than either YUI or jQuery. For example, Prototype implements each() as a method of the Array object, so the example above could use

courses.each(fn)

in place of

Y.Array.each(courses, fn)

or

$.each(courses, fn)

which is, IMHO, a lot nicer. This isn't a suggestion that we should switch to Prototype, but merely a demonstration that each framework has it's relative merits, and which is "best" is probably largely subjective.

Mark Johnson 16:56, 2 November 2011 (WST)