Tag: js

  • Managing js dependencies and the importance of global-scoped libraries

    In performing some cleanup, abstraction and upkeep on the 0.3 release of the BBB player (soon to be 0.3.1), I’ve been working on expanding the testing functionality. In doing so, I’m pushing the test code from the testing page (test.html) into its own external JavaScript file (test.js). Trouble is, I want to keep this code separate from the main BBB.js library, but I need to address the fact that test.js is dependent on BBB.js. It’s possible to put them all in the same file, but that’s a lot of overhead for the 99% of the time that the testing framework isn’t required. So we can put them in different files and trust that on every HTML page that test.js is loaded, BBB.js is loaded before it. This is bad and error-prone!

    What to do?

    Turns out, we can load BBB.js dynamically as needed from other js files. As many js developers know, you can check if a variable has been used by doing this:

    typeof(bbb) === 'undefined'

    This statement will return true if bbb has not been declared or assigned to. So with this, we’re already part-way there: we know if our library (bbb) was not included on our test page. So from here, we just need to load it. Guess we’ll have to make another XMLHttpRequest to get it!

    if (typeof(bbb) === 'undefined') { // No record of global BBB, must've been forgotten on test page
      var xhr = new XMLHttpRequest();
    
      xhr.onreadystatechange = function() {
        if (xhr.readyState===4) {
          // Execute BBB.js and populate in bbb variable
        }
      }
      xhr.open("GET", '../BBB.js', false); // Not async, test code below depends on having global bbb object
      xhr.send(null);
    }

    So now we have a way of getting BBB.js if the HTML page doesn’t do it. However, BBB.js comes back as text. if put at the top of a file, the code above will simply retrieve BBB.js from the server and have it sit there. So we’ll have to execute the text as JavaScript. And for that… we use EVAL.

    Long-touted as lazy, dangerous and inefficient, eval is great for executing text as JavaScript code. It’s often misused aand security and performance almost always take a hit. If you tell someone you’re using eval in your library, the natural reaction to expect is “REALLY?”. This, of course, after the typical recoil in horror. Turns out, resolving dependencies is one of the few things it’s good for. When added to the code above, it will execute the requested js file and (in the case of a library like BBB) will have a variable stocked full with your code and ready to use. Package the whole thing into a function and you have something like this:

    function requireBBB(fileRef) {
      if (typeof(bbb) === 'undefined') { // No record of global BBB, must've been forgotten on test page
        var xhr = new XMLHttpRequest();
    
        xhr.onreadystatechange = function() {
          if (xhr.readyState===4) {
            eval(xhr.responseText); // Execute BBB.js
          }
        }
        xhr.open("GET", fileRef, false); // Not async, test code below depends on having global bbb object
        xhr.send(null);
      }
    }
    
    requireBBB('../BBB.js');

    And this works. Or at least it should. It didn’t for me. Convinced that it was something wrong with this function, I plugged alerts on every other line of code. I used Firebug. I used everything at my disposal to figure it out. Finally, after far too long, I got a hunch. I pulled up the BBB.js file and found the first line to be this:

    var bbb = (function(){

    Seems  innocent. Run a self-executing anonymous function and store the result in locally-scoped variable bbb. Wait, what? We’d never run BBB.js outside of global page scope, so local scope was always window and it had always worked. However, eval’ing the library code from the xhr.onreadystate function, bbb isn’t put on the global window because that’s not the context the code is executing in. Instead, bbb became a member of xhr. The function then finished and bbb blipped out of existence as quick as it blipped in. A change in BBB.js to this:

    bbb = (function(){

    And everything works. The variable is declared as implicitly global and can be accessed elsewhere. The moral of the story: always store your library at global scope if you want to manage dependencies or otherwise avoid scope-related issues.

    There is a downside to dynamic loading: performance. Every XmlHttpRequest means a hit on performance; ideally you want to put all js code in it’s own file, and the same for CSS. In fact, using this on-demand js-loading breaks #1 on Yahoo’s list of “Best Practices for Speeding Up You’re Website“. However, since users won’t really be needing the testing framework (and it will just be extra JS for the browser to work with if included in BBB.js) this should be okay. While I have yet to try it, I hope to do a bevy of performance testing using (amongst other tools) the Firefox extension YSlow before formally pushing out 0.3.1. Even without formal profiling, we’ve already caught a few other minor improvements to performance!

    Stay tuned for 0.3.1…

     

    UPDATE (Jan 20, 2011): After working on popcorn and learning a few things, there is a much better way than the use of eval. Seems JSONP makes use of dynamic script tags as a way of issues cross-origin requests. This works equally well to dynamically load libraries (like jQuery) and looks much cleaner than XHR:

    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.src = "http://code.jquery.com/jquery-latest.min.js";
    script.type = "text/javascript";
    head.insertBefore( script, head.firstChild );
  • BBB Player 0.3

    With December upon us, it’s time for the third iteration over the BBB Player. And this one shan’t disappoint. My partner’s release notes forthcoming, I’ll be focused on discussing my own tasks. If you’ll recall, the original list of items tasked to me looked like this:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)

    With the first three being completed by my last BBB-related update. Since then, the list grew to a few more to include:

    • Refactor: Automate library loading via DOMContentLoaded event
    • Refactor: Create “storage” module from existing functions

    With these 7 tasks (plus minor fixes here and there) the code base underwent quit a few changes, however the core API remained unbroken with existing code. For example, while the library can hook into the DOMContentLoaded event to load automatically, one can still manually call the init() function from the onload event of the body to load all internal values. See both options below:

    bbb.setupWhenReady({playerId: "player", tocId: "tblOfContents", chapterStorage: "server.php", formDivId: "formDiv", statistics: true, watermark: true});
    
    // OR
    <body onload="bbb.init({playerId: 'player', tocId: 'tblOfContents', chapterStorage: 'server.php', statistics: true, watermark: true});">

    Internally, setupWhenReady() calls the init() function with the passed parameters, but the function may be called anywhere within the HTML document. Also, setupWhenReady() will call the bbb.onReady() function when finished. Override this method to execute page-specific logic once the library has loaded. Another callback function, bbb.onChangeVideo(currChap), is called when changing chapters. Taking one argument (the new chapter), it can be used to update parts of the page with internal information about the playing video. If overriding, expect to receive a Bookmark object. See it all in use when it’s used to autopopulate the chapter creation form.

    Another great usability feature now implemented is optional subtitles. I discussed this at length in my last BBB blog post, but the VideoJS library we’re using (at least version 1.4) would not allow for subtitles to be optional. If a file is specified, they would play. After a bit of work on Video.js however, they’re now togglable. As part of implementing this, a bug was also found and fixed where subtitles will now display properly when scrolling backwards through the video. Both of these fixes have since also been forwarded to the VideoJS team.

    The big thing about this release for me, however, is the creation of popcorn metadata (in the bbb.generator module). Presently supporting 7 command types (Wikipedia, Flickr, Google News, LastFM, Twitter, Video Tag, and Footnote), the library internally handles all form generation and functionality, however placement can be determined by supplying the library with the id of a div on the page in which to place the form. Also, using this div ID and CSS Selectors, it’s easy to style the generated form at the page level. Unfortunately, hosting limitations to the tune of no file IO in JavaScript, no PUT requests to servers, and no file writing means it’s undemoable on its current host, but feel free to check it out all the same!

    And so that being that, my portion of the 0.3 release of the BBB player is complete. Minor fixes or structural changes for this release include:

    • Moving free-floating page-level setCookie, getCookie functions into bbb.storage module
    • Beginning of a bbb.chapters module for storing and working with chapters (though functions from 0.2 remain outside at this time to maintain a consistent API)
    • Error checking, validation and output formatting for chapter and popcorn metadata creation.

    See the code base on my github, especially the 0.3 branch. While this work was all done through a class with Dave, I don’t want to be done quite yet! Hopefully there’s much more work, development and contribution opportunities ahead. Luckily, with open source, that’s always the case 🙂

  • InnerHTML vs. DOM Manipulation

    In working on pushing out my 0.3 Release for the BBB Player, I’ve had to look a great deal into element generation and the best ways to do it. But how exactly do you measure “best”? Is best the best performance, the least lines of code, or the most compatible? And if we go by all three, what’s the answer then?

    Usage and Support

    Turns out, this has been a long-lasting debate on the interwebs. Internet Explorer’s handy but non-standard innerHTML property lets you do things quick and painlessly by specifying the HTML code as a string right in the Javascript. You can build this dynamically then via simple string concatenation. It’s short and quick, but not guaranteed to be supported everywhere (though it happens to be by just about every browser out there). Seems to be a carry over from IE5.5 when IE had 95% of the market. What were other browser makers to do to have their browser work with a web site but to implement innerHTML? Besides though, watch how friendly it is to use.

    var mySpanId = "spanID";
    var myDiv = document.getElementById("myDivId");
    myDiv.innerHTML = '<span id="'+mySpanId+'">Embedded Content</span>';

    A quirk about it though is that as simple as it is, it will not work with tables. A price to pay for simplicity, but unfortunately quirks happens when the specifications are not standardized. Now lets compare DOM Manipulation:

    var mySpanId = "spanID";
    var myDiv = document.getElementById("myDivId");
    var mySpan = document.createElement('span');
    var spanContent = document.createTextNode('Embedded Content');
    mySpan.id = mySpanId;
    mySpan.appendChild(spanContent);
    myDiv.appendChild(mySpan);

    Longer code, that’s for sure. But this is guaranteed to work on every browser, now and forever (well, almost). Though there are still a few cross-browser quirks. Turns out you can create input elements both ways, but IE does not support the DOM methods outlined above; it balks if you try and set the type of the newly created input element like so:

    var myInput = document.createElement('input');
    myInput.type = 'text';

    And there is no pure DOM way around this. No using myInput[“type”] = ‘text’, no nothing. In IE, you simply can’t change the type of an input element once it’s been created. As John Resig notes, not even jQuery is immune, and so he had to implement a work around in his API. Turns out you must specify the entire element in HTML markup for it to work:

    var myInput = document.createElement('<input type="text" />');

    So there are compatibility concerns on both sides of the fence, but one thing DOM does which innerHTML doesn’t is allow for easy mixing and merging of Document Fragments: collections of nodes outside of the main document structure which can be built, appended and removed as children in any order desired. Contrast this with innerHTML where you need to know what you’re concatenating as strings before doing it, unless you want to worry about complicated substringing later on.

    While in the end it’s personal preference, the standard compliance and dynamic changing nature of Document fragments gives DOM the nod in my book.

    Performance

    So with that being that, how do things compare in performance? Turns out there’s been a lot of benchmarking done on this. John Resig has done some studies comparing the performance of appending Document fragments as opposed to appending many individual nodes. Testing on a wide variety of browsers, he showed that it can vary from two-four times faster using Document Fragments. So lesson learned: Favour Document Fragment usage. They result in only a single redraw operation for the browser, since there is only one modification of the displayed nodes.

    Before going on to other studies though, another interesting difference between document fragments and individual nodes: changes to individual nodes after appending them as children persist to the appended-to node, but modifying a document fragment after appending will not. In fact, appending a document fragment as a child will result in clearing the nodes from it in the process of appending to the parent. Clearly, this is the result of pointers and linked lists being worked with, but it’s still good to know that upon appending a document fragment, the parent element is considered the sole owner of the child nodes.

    On to other studies! Several years ago Peter Paul Koch conducted his own comparing IE5.5, 6 and 7 to FF 2 and 3 beta, Opera 9.5 Safari 3. At that time, innerHTML won. It makes sense in a way, IE was the monster browser of the time, why not optimize a browser to use IE’s innerHTML well? Well, a little bit later, once a full iteration of browser development had occurred (including the emergence of Google Chrome), Andrew Hedges conducted his own studies. Turns out he was able to show DOM pulling much closer, to the point that the difference was negligible. Perhaps this was the document fragment factor or the result of other optimizations not included in PPK’s method?

    In the end, they are now almost interchangeable in speed. So which to use? Andrew Hedges clearly prefers DOM, but as for you and I, pick your pony. I’ll be sticking with DOM, it’s a bit more code but a lot less compatibility to worry about in years to come.

    myDiv