Javascript: Introducing Using (.js)

by Jon 4/12/2008 3:37:00 PM

I'm releasing v1.0 of using.js which introduces a new way of declaring dependency scripts in Javscript.

http://www.jondavis.net/codeprojects/using.js/

The goals of using.js are to:

  • Seperate script dependencies from HTML markup (let the script framework figure out the dependencies it needs, not the designer).
  • Make script referencing as simple and easy as possible (no need to manage the HTML files)
  • Lazy load the scripts and not load them until and unless they are actually needed at runtime  

The way it works is simple. Add a <script src="using.js"> reference to the <head> tag:

<html>
  <head>
    <script type="text/javascript" language="javascript" src="using.js"></script>
    <script type="text/javascript" language="javascript">
      // your script here
    </script>
  </head>
  <body> .. </body>
</html>
 

Then in your script, register your potential dependencies. (These will not get loaded until they get used!) 

using.register("jquery", "/scripts/jquery-1.2.3.js"); 

Finally, when you need to begin invoking some functionality that requires your dependency invoke using():

using("jquery"); // loads jQuery and de-registers jQuery from using
$("a").css("text-decoration", "none");

using("jquery"); // redundant calls to using() won't repeat fetch of jQuery because jquery was de-registered from using
$("a").css("color", "green");

Note that this is only synchronous if the global value of using.wait is 0 (the default). You can reference scripts on external domains if you precede the URL in the using.register() statement with true and/or with an integer milliseconds value, or if you set the global using.wait to something like 500 or 1000, but then you must write your dependency usage scripts with a callback. (UPDATE: v1.0.1: Simply providing a callback will also make the load asynchronous.) No problem, here's how it's done:

using.register("jquery", true, "http://cachefile.net/scripts/jquery-1.2.3.js");
using("jquery", function() {
  $("a").css("text-decoration", "none"); //async callback
});

Oh, and by the way, using.register() supports multiple dependency script URLs.

using.register('multi', // 'multi' is the name
    '/scripts/dep1.js', // dep1.js is the first dependency
    '/scripts/dep2.js'  // dep2.js is the secon dependency
  );

UPDATE: I just mostly rewrote using.js. Now with v1.1 you can now add subdependencies, like so:

using.register('jquery-blockUI', true,
  'http://cachefile.net/scripts/jquery/plugins/blockUI/2.02/jquery.blockUI.js'
 ).requires('jquery');

Basically what the new .requires() functionality will do is when you invoke using('jquery-blockUI'); it will also load up jquery first.

UPDATE 2: With v1.2 I've added several new additional touches. Now you don't *have* to declare your subdependencies with using.register(), you can just say:

using('jquery', 'jquery-blockUI', function() {
  $.blockUI();
});

This assumes that jQuery and blockUI have both been registered, the latter without the .requires('jquery') invocation.

That said, though, you don't even have to call .register anymore if you don't want to:

using('url(http://cachefile.net/scripts/jquery/1.2.3/jquery-1.2.3.js)', function() {
  alert($.fn.jquery);
});

There are also two new features that *should* work but I haven't written tests yet:

  1. using.register([json object]); // see using.prototype.Registration
    • object members, and the arguments for the compatible using.prototype.Registration prototype function, are both:
      1. name (string)
      2. version (string, format "1.2.3")
      3. remote (boolean, true if external domain; invoke requires callback)
      4. asyncWait (integer, milliseconds for imposed async; invoke requires callback)
      5. urls (string array)
  2. Registration chaining:
    • using
        .register("myScript", "/myscript.js")
        .register("myOtherScript", "/myotherscript.js").requires('myScript')
        .register("bob's script", "/bob.js");

UPDATE 3: v1.3 fixes the using('url(..')) functionality so that a script loaded this way is remembered so that is not fetched again if the same URL is referenced in the same way again. This is the reverse of the using.register() behavior, where if a script is loaded its registration is "forgotten". Also made sure that multiple script URLs listed in using('url(..)', 'url(..)'), function(){}); is supported correctly.

If for some strange reason you want the script at the same URL to be re-fetched, try this unsupported hack that might not be available tomorrow:

using.__durls['http://my/url.js'] = undefined;

UPDATE 3.1: V1.3.1 should hopefully fix the "not enough arguments" error that some Firefox users have been having. I was never able to reproduce this, but I did discover after doing some research that Firefox supposedly expects null to be passed into xhr.send(). I guess some systems suffered from this while I didn't. At any rate, I'm passing null now.

If you have bug reports or suggestions, please post comments here or e-mail me at jon@jondavis.net.

kick it on DotNetKicks.com

Currently rated 4.2 by 10 people

  • Currently 4.2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Software Development | Web Development

Related posts

Comments

4/8/2008 9:29:43 AM

Kirill Chilingarashvili

Thanks for a great code!
It would be nice if the script have the ability to unregister script - remove the script node from the document.
I thought about using the code in ASP.NET AJAX scenarios where I want to load sometimes UI from AJAX Web Services. Often in this scenarios I am loading UI that needs some script - I could use using to load dynamically the script when I need this, and unregister the script (remove from header) when I dont need this anymore.
Do you think this would be a wrong approach?
I have a pages that are loaded only once - and then everything is loaded using web services, this means user can stay on the page for a long period of time. I think that too much script includes can be collected after a period of time.
And one more question. When loading the script with using.js - I noticed you use AJAX to do that - does script loaded every time - or it uses browser cache to retrieve already loaded scripts?
Thanks.

Kirill Chilingarashvili ge

4/8/2008 9:51:45 AM

Jon

TODO note to self: Need to make using() be asynchronous, *always* (even if wait = something small like 1) if a callback param is passed in!!

Jon us

4/8/2008 9:59:20 AM

Jon

@Kirill: "It would be nice if the script have the ability to unregister script ... unregister the script (remove from header) when I dont need this anymore" .. I'm not sure exactly what you're saying; once a script has been loaded, it is processed and executes within the scope of the window object. At that point, it persists for the duration of the page's lifetime, so removing the script node won't do anything. If I'm mistaken about that, I'll be floored..

Using.js as presented takes the opposite approach; the script blocks are not created until they are needed.

"When loading the script with using.js - I noticed you use AJAX to do that - does script loaded every time - or it uses browser cache to retrieve already loaded scripts?"
As far as I know, AJAX requests are performed in the context of be browser (using browser cache). You can force it not to be cached by either adding a dynamically created random querystring field value on the URL, or by setting the no-cache setting in ASP.NET's response: Response.Cache.SetCacheability(HttpCacheability.NoCache);.

HTH,
Jon

Jon us

4/8/2008 11:32:13 AM

Kirill Chilingarashvili

Jon,
Thanks for answers,

I will recheck whether script includes are unloaded from document when using ASP.NET AJAX controls. I thought they are removed when no more needed. I think I have seen this when debugging the pages using FireBug, but I am not sure.

Thanks again.

Kirill Chilingarashvili ge

4/8/2008 4:51:50 PM

shi

Hi,

Thanks for sharing your code.
Could you list the supported browsers?
The script injection with callback may not work
in Safari as it doesn't seem to fire the onload event.
but perhaps this has been fixed

shi

4/8/2008 7:18:07 PM

Jon

using.js has no dependency upon onload.

These browsers have been tested and found to pass:

* Safari 3.1 (Windows)
* Firefox 2 (Windows)
* IE 7
* IE 8 Beta 1
* Opera 9.26 (Windows)

These browsers have been tested and found to fail:

* Safari 1.2 (Mac) << did not pass at all
* Firefox 1.5 (Mac) << passed only with async call

Jon us

4/8/2008 7:25:46 PM

Jon

Firefox 2 for Mac also failed, passing only with async call (moo tools test).

I would recommend using async calls everywhere using() is used, as with

using("resource", myFunction);
function myFunction() {
// do stuff
}

Jon us

4/8/2008 8:31:10 PM

BTM

It fails on Firefox 2 Win (2.0.0.13) - Firebug says:

uncaught exception: [Exception... "Not enough arguments" nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)" location: "JS frame :: http://cachefile.net/scripts/using/1.0/using.js :: anonymous :: line 107" data: no]

On loading helloworld.js/nocallback/multiple-deps on your example pages. Only the moo one works.

BTM pl

4/8/2008 8:56:31 PM

Jon

This might actually be a minor bug in using.js and/or with the tests (rather than a limitation of the browser). Give me a couple hours, I'll update...

Jon us

4/8/2008 10:08:26 PM

Christian

You should combine it with Doloto research.microsoft.com/.../view.aspx?tr_id=1402 which is intended to generate your dependent scripts automatically out of your "plain" source code to load them on demand.

Christian de

4/8/2008 10:38:00 PM

Jon

Yeh this is weird .. I'm having a hell of a time reproducing that error, BTM. Works for me both at http://cachefile.net/scripts/using/1.0/test.html" rel="nofollow">http://cachefile.net/scripts/using/1.0/test.html and at www.jondavis.net/codeprojects/using.js/test.html

See if the update at http://cachefile.net/scripts/using/1.0/test.html" rel="nofollow">http://cachefile.net/scripts/using/1.0/test.html works for you. This update ensures that using the using() method with a callback param will always be asynchronous. Only lightly tested .. need sleep! Smile

Jon us

4/8/2008 10:39:18 PM

Jon

not sure what that "nofollow" stuff is in my comments, just use the URL that follows "nofollow", rather than the one that precedes it. Wink

Jon us

4/8/2008 10:39:50 PM

Jon

gah .. the updated test is http://cachefile.net/scripts/using/1.0.1/test.html

Jon us

4/8/2008 10:41:02 PM

Simon Willison

I strongly recommend never using async=false in production code - it freezes up the UI entirely on most browsers, which is unacceptable.

Simon Willison gb

4/9/2008 5:47:06 AM

BTM

Now (http://cachefile.net/scripts/using/1.0.1/test.html) all thre buttons work, but the forth on still dosn't.

BTM pl

4/9/2008 5:51:19 AM

Jon

@Simon - "Unacceptable" is what I blogged about already, re here: http://www.jondavis.net/codeprojects/synctest/ However, it's necessary to execute synchronously if you use using("lib"Wink; and the code that follows expects it to be loaded. The recommendation is to perform an async call with using() by passing in a callback function. Otherwise, it's the developer's call as to whether the synchronous call is "unacceptable"; you don't know whether this is a locally executed, offline app, for example.

@BTM - That's bizzare. Not sure why it wouldn't work for you but it would work for me.. (??) I'm using the same version of FF.

Jon us

4/9/2008 6:16:56 AM

Jon

@BTM: I used the defaultAsyncWait rather than the value of 1 for the async wait. I overwrote v1.0.1. Give it one more try? (Don't forget to clear your cache.)

Jon us

4/9/2008 6:37:20 AM

Jonathan Bond-Caron

You might be interested in looking at:

www.openajax.org/.../RegisterResource

There will be a release around May-June at http://openmv.com, the actual api will be:

mv.provide()
mv.load()
mv.registerResource()
mv.getResource()

Jonathan Bond-Caron ca

4/9/2008 6:53:45 AM

Irfan

dont you think it will make the calls slow. I tried it with extjs action but it was too slow when the event was fired for the first time.

Irfan pk

4/9/2008 8:29:14 AM

mdmadph

I love it! Been wanting something like this for a long time -- I'm so tired of having to put script dependencies in markup, it always feels so messy.

However, I'm having trouble getting it to work with my web apps -- they depend on a lot of weird onReady scripts and whatnot, so I'm still trying to figure out how to integrate your script...

And the multiple-dependencies bug is happening for me too -- are we all using the same version of Firebug here, too? (I'm using 1.1.0b12)

mdmadph us

4/9/2008 10:35:39 AM

Kris Kowal

Have you seen my "modules.js"? It's an XHR/eval module loader that doesn't require pre-registration, manages dependencies, and supports build-time dependency aggregation.

http://modulesjs.com

One thing it doesn't do is asynchronous loads if you pass a continuation into "require", although it's designed to support this eventually. In my experience, blocking "include" calls are quite elegant and you don't see the penalty of the load if you pre-package your most relevant dependencies (I'm still working on the packager).

However, if you load the module loader module, you can actually unload one of the module singleton objects so that the next person to require it gets a new version.

Kris Kowal us

4/10/2008 1:02:38 AM

Eric Nguyen

This is fantastic, Jon. I currently work at http://instructables.com, and we wrote a GPL'ed library called JSLoad that does a similar thing. I'm definitely going to look through your code and tests to see if we can improve what we currently have running. We'd be happy to hear your suggestions, as well. Kris, I'll also be looking at modulejs.

I posted a similar comment over on the Ajaxian blog:

"One difference between using and JSLoad seems to be that the dependencies are managed centrally by a JSLoad singleton; we work with many existing libraries and scripts, and adding dependency code to files we didn't create seemed like a maintenance and upgrading headache."

"JSLoad was also written with flexibility in mind. Dependencies come in the form of "tags", which allows for all kinds dependency structures that are independent of the js files themselves. This was very useful since I was handed a good amount of legacy code when I started; JSLoad allowed me to encapsulate the code according to initial guesses and refactor for efficiency and maintainability over time."

Here's the link to JSLoad: http://www.instructables.com/blog/B2OLM73F5LDFN2Z/

Eric Nguyen us

4/10/2008 1:24:03 AM

Michael Lee

Jon,
Nice attempt at implementing using for JavaScript.

I understand the thrill of implementing your own solution, but you may want to look at Ajile [ http://ajile.iskitz.com/ ]. It's a cross-browser, JavaScript module I've written that simplifies JavaScript inclusion and dependency definitions while also offering namespacing.

Kirill is right that without removing included scripts you can end up with a bloated DOM. Ajile automatically takes care of this via it's cloaking feature, and allows the developer to easily configure whether cloaking is used.

As far as browser caching goes, Ajile also provides an easy way to disable browser caching if desired.

Check out ajile.iskitz.com for an overview, demos, download, and documentation.

For a quick overview see ajaxpatterns.org/.../index.php


HTH,
Michael

BTW, what's the deal with the links in comments on this blog? The preview never seems to come out right, it just seems to make the links messy...

Michael Lee us

4/10/2008 2:35:21 AM

Jon

Not trying to build a grandiose library here, what using.js is about, isn't about a framework or even about a "reusable function", etc. What I'm really trying to introduce here is a alternative scripting methodology that is light enough make code both self-documenting and tersely self-dependent. There are a lot of late-loaders out there, for instance, lots of ways to skin the cat, but what I was trying to introduce was a coding *style* of simplicity where late-loading comes into play, something similar in form, structure, and purpose to C/C++/Java "include" statement, or C#'s "using" statement.

That said, using.js is out and I'm thinking a bit about what I've done. A few changes I'm aware of or else thinking about are ..

* Need to drop "wait" usage for local scripts loaded asynchronously by going back to using an AJAX call rather than src="" call, and make the boolean "true" a prerequisite parameter on the using.register() function before a wat is ever even needed.

* Need to rewrite the output objects of using.register() to a defined structure / model rather than what I have now which is a name/value pair where value is either string or object presumed to be an array and the array values are typeof()-evaluated as integer (async post-load milliseconds), boolean (true for remote domain), or string for URL. Bleah, too disorganized!

* Need to actually test and finalize the half-implemented "sender" param!!

* Want to add support for subdependencies, i.e. maybe something like ..

using.register("jquery",
"/scripst/jquery.js"Wink;
using.register("jquery.blockui",
"/scripts/jquery-blockui.js"
).dependsOn("jquery"Wink;

using("jquery.blockui", function() { // loads *both* jquery and blockUI
$("myTable"Wink.blockUI();
});

Jon us

4/10/2008 2:36:13 AM

Jon

darn smilies Tong

Jon us

4/10/2008 3:38:54 AM

Jon

@Jonathan Bond-Caron: Nice. And the Open Ajax Alliance is something I should be watching more. FYI I've been trying to keep cachefile.net up-to-date with OAA bits; am I missing anything? http://cachefile.net/scripts/OpenAjax/

@Irfan: I wouldn't bind using() to a UI event in production unless the UI associated with it is already suggesting a slow AJAX call like a datagrid update. Othwerwise, IMO one should use using() in the page's initialization script.

@mdmadph: I've been building on FF v2, not really sure what you're seeing. The lowest version I'll support is v1.5.

@Kris Kowal: No, but it looks interesting. There's also http://jsloader.com which was an "inspiration" (except that I might still want to do something with jsloader.com)

@Eric Nguyen: Hope you find using.js useful; I like instructibles.com, btw!! I'll check out JSLoad; no relation to JSLoader.com I don't suppose?

@Michael Lee: "I understand the thrill of implementing your own solution, but.." Heh .. actually I just thought "using('mylib');" as a synchronous late loading technique was really elegant so I implemented it as I saw fit, blogged it, and mentioned it to Dion @ Ajaxian. No thrills.. some pride in the elegance of it, though, whether others see it or not. Tong Ajile looks handy though.

Jon us

4/10/2008 4:57:41 PM

Tjerk

Is use the dojo library for exactly this lazy loading using dojo.require and dojo.provide.
However i tweaked those method and got my own implementation. I learned that dojo retrieves the javascript via ajax and then parses them... however this didnt work in IE6 as expected..

For example if i have a script a.js
--
var A="test";
--

And then i include it as follow
--
require("a.js"Wink;
alertInnocent;
--
Then i would get an undefined alertbox.. however by tweaking a.js to:
--
self.A="TEST";
--
The problem dissapeared. Somehow you need the self. for global variables when they are
included using ajax.

I do not know whether u are using this technique but maybe you could make your library even compatiable with IE6 which would make it more useable in production websites.

Tjerk

4/12/2008 6:20:05 AM

Torsten Raudssus

And where is the advantage now?

Torsten Raudssus de

4/12/2008 8:12:38 AM

pingback

Pingback from blogs.byte-force.com

"Using" for javascript - «XOR's Post»

blogs.byte-force.com

4/12/2008 8:15:56 AM

Jon

OK I've pretty much rewrtten most of using.js now (now @ 7kb), based on the changes I proposed a few comments ago. Still more testing to do. We're now at v1.1; the changes are not breaking changes, or shouldn't be, so that's why not v2.0.

Jon us

4/12/2008 8:32:26 AM

Jon

FYI this change introduces subdependencies, i.e.
using.register( .. ).requires( .. );
.. where requires() parameter is the dependency script's logical name.

Jon us

4/13/2008 6:17:42 PM

Kevin Deenanauth

This is great! We're considering using this, but were wondering what license are you publishing this as?

Kevin Deenanauth us

4/14/2008 10:37:48 AM

Jon Davis

It's currently on the OCIFTSAL License, subject to change. Feel free to download and use it; once the license is changed the new license will apply to newer versions.

(OCIFTSAL = Oh Crap I Forgot To Select A License)

Jon Davis us

4/14/2008 10:42:44 AM

Jon Davis

The using('url(...)'); executes each time. Should I do a reverse registration on this? Normally, if a resource is registered, once it's used it's "forgotten" so that subsequent "using" statements on that resource are "shrugged off" (the library is already loaded). The using('url(...)') skips the registration altogether; should this be an "always load" routine or a "remember that I loaded this URL" thing so that it remembers that the URL has already been loaded and skips it next time the same URL is referenced?

Right now I'm thinking towards "remember it, to skip it next time".

Jon Davis us

4/21/2008 2:36:05 PM

Leon

You are great!

Thanks for this code, I can really use it in the Symfony-Extjs Plugin I am working on, in which I never know which modules I want to load in advance, and I don't want to load them all at the initialisation.

I want to be lazy, and now I can :-D

by the way, As a non-Javascript-expert I don't understand where the dollar stands for, in:
$("a"Wink.css("text-decoration"
or
alert($.fn.jquery);

I might be lazy, but I do wanna learn ;-)

Leon nl

4/21/2008 3:12:53 PM

Jon

Leon, thanks for the comments.

"$" is the name of the jQuery function, i.e.
window.jQuery = window.$ = function () { .. };

Jon us

4/21/2008 5:25:48 PM

Leon

Thanks for the info, I definitely have to look into jQuery better.

At the moment I can give a (crippled) demo of where I am using the using-functionality for:
the plugin for Symfony I am working on generates extjs-code for grids and edit-pages, I wanted these edit-pages to be loaded dynamically when a user pressed the edit/create buttons and now that works. (due to have rewriting of the code the loading isn't implemented yet, but your code works great!)

The demo can be found at:
http://fun4me.demon.nl/test/test_dev.php
you can press Add city (not country yet) or click on the name of a city (doesn't load the city yet...)
The first time the city-layout is requested it is loaded, after that it is known Laughing

Leon nl

4/23/2008 7:53:57 PM

Johan

Hey, thanks a lot for this. I found this one problem when using Firefox 2.0.0.14. I get this error in the error console:
"Error: uncaught exception: [Exception... "Not enough arguments" nsresult: "0x80570001 (NS_ERROR_XPC_NOT_ENOUGH_ARGS)" location: "JS frame :: http://liveween.com/philost/js/using.js :: anonymous :: line 233" data: no]"

Do you have an idea what it might be caused by? I would be happy to provide any information you might need.

Johan se

4/23/2008 11:06:57 PM

Jon Davis

This is the same error previously reported here; I haven't been able to reproduce it. What are your steps to reproduce? And what OS?

Jon Davis us

4/25/2008 2:06:05 AM

Jon

Firefox requires a null argument for xhr.send(). Track it down and just type "null" in xhr.send(). I'll update it in the next revision.

Jon us

4/29/2008 2:13:16 AM

Jose

Hi Jon, the idea of the library is great! but two things: I think 13Kb is to much, jQuery is a big js library and just has 29Kb, and when I dont have firebug enabled, using doesnt work Frown, I test it on several computers and alway do the same. Thanks!

Jose ar

4/29/2008 10:48:26 AM

Jon

jQuery is *not* 29kb, unless you're using a very, VERY old version.

jQuery is 94k. Minified, it becomes 52.8k, but for that matter, using.js when minified becomes 7kb. Compressed, using.js can be as small as 3kb.

Jon us

5/6/2008 8:51:27 AM

Johan

Thanks Jon. Finally got in touch with the guy who had the problem in Firefox and a null argument solved it.

Johan se

Add comment


(Will show your Gravatar icon)  

  Country flag





Live preview

5/11/2008 1:20:13 PM


 

Powered by BlogEngine.NET 1.2.0.0
Theme by Mads Kristensen

About the author

Jon Davis Jon Davis (aka "stimpy77") is a senior software engineer for the online enthusiast division of a major magazine publishing company, and has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps. Lately, Jon's professional focus has been on C#, ASP.NET, Windows services, WCF, and implementations of Lucene.net and telligent's Community Server for multiple web sites.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

E-mail me Send mail

Calendar

<<  May 2008  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar

Pages

    Recent posts

    Recent comments

    Authors

    Tags