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
(Added missing on())
Line 29: Line 29:
             M.block_quickcourselist.search(searchstring);
             M.block_quickcourselist.search(searchstring);
         });
         });
         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');

Revision as of 09:39, 2 November 2011

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');
           M.block_quickcourselist.search(searchstring);
       });
       Y.one('#quickcourseform').on('submit', function(e) {
           e.preventDefault();
           var searchstring = e.target.getById('quickcourselistsearch').get('value');
           M.block_quickcourselist.search(searchstring);
       });
   }

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. 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 calls. However 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.

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,
           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. 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 this 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.

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)

DOM manipulation

YUI: success: function(id, o) {

                   var courses = Y.JSON.parse(o.responseText);
                   var block = M.block_quickcourselist;

list = Y.Node.create('

    '); if (courses.length > 0) { Y.Array.each(courses, function(course) { Y.Node.create('
  • <a href="'+M.cfg.wwwroot+'/course/view.php?id='+course.id+'">'+course.shortname+' '+course.fullname+'</a>
  • ').appendTo(list); }); } Y.one('#quickcourselist').replace(list); list.setAttribute('id', 'quickcourselist'); block.progress.setStyle('visibility', 'hidden'); },
    jQuery: success: function(courses, status) { list = $('
      '); if (courses.length > 0) { $.each(courses, function(key, course) { $('
    • <a href="'+M.cfg.wwwroot+'/course/view.php?id='+course.id+'">'+course.shortname+' '+course.fullname+'</a>
    • ').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)