with Imagination: by Dustin Diaz

./with Imagination

A JavaScript, CSS, XHTML web log focusing on usability and accessibility by Dustin Diaz

Roll out your own JavaScript Interfaces

Wednesday, October 3rd, 2007

There are times when using a JavaScript library is called for. Building large web applications that use a wide array of utility functions that help aid in developing multi-tiered class systems, advanced UI components, complex event models, and heavy use of DOM scripting helpers. Yep. Those are all great.
However, there are other times when you don’t need all that. And often what we end up doing is just importing a few of our favorite functions as globals, and work off those. But what ends up happening in this case is that we lose the particular style that these libraries offer. For instance, I’d still like to be able to do something like this without a library.

Sample application code

$('foo', 'bar').on('click', function(e) {
    $(this).css({
        color: 'green',
        fontSize: '2em'
    }).addClass('active');
});

Starting from scratch

Since we all have a love for the old dollar function introduced by Prototype, we can use that as a base.

Original Prototype $ function

function $() {
	var elements = [];
	for (var i = 0; i < arguments.length; i++) {
		var element = arguments[i];
		if (typeof element == 'string')
			element = document.getElementById(element);
		if (arguments.length == 1)
			return element;
		elements.push(element);
	}
	return elements;
}

However, instead of treating this as a normal function, we’ll transform it into a constructor, then assign instance methods. Then of course to achieve chainability, all we have to do is simply return the instance reference. We’ll start off by creating a private interface, then adapt the dollar function to return this private constructor.

Creating the interface

(function() {
  // private constructor
  function _$(els) {
    this.elements = [];
    for (var i=0; i<els.length; i++) {
      var element = els[i];
      if (typeof element == 'string') {
        element = document.getElementById(element);
      }
      this.elements.push(element);
    }
    return this;
  }

  _$.prototype = {
    each: function(fn) {
      for ( var i = 0, len = this.elements.length; i<len; ++i ) {
        fn.call(this, this.elements[i]);
      }
      return this;
    },
    setStyle: function(prop, val) {
      this.each(function(el) {
        el.style[prop] = val;
      });
      return this;
    },
    addClass: function(className) {
      this.each(function(el) {
        el.className += ' '+className;
      });
      return this;
    },
    on: function(type, fn) {
      var listen = function(el) {
        if (window.addEventListener) {
          el.addEventListener(type, fn, false);
        } else if (window.attachEvent) {
          el.attachEvent('on'+type, function() {
            fn.call(el, window.event);
          });
        }
      };
      this.each(function(el) {
        listen(el);
      });
      return this;
    },
    css: function(o) {
      var that = this;
      this.each(function(el) {
        for (var prop in o) {
          console.log(prop);
          that.setStyle(prop, o[prop]);
        }
      });
      return this;
    }
  };
  window.$ = function() {
    return new _$(arguments);
  }
})();

Short and concise! Check out an implementation of this code in action to see how easy it is to start rolling out your own personal interfaces. Cheers.

41 Responses to “Roll out your own JavaScript Interfaces”

  1. Jon

    You make it look so easy!

  2. Roll out your own JavaScript Interfaces

    [...] Roll out your own JavaScript Interfaces [...]

  3. Felix

    I love the console.log(prop) is still in your code. Maybe one should generically implement the console object in this sort of interface ;)

  4. Dustin Diaz

    @Jon: Because it always has been this easy :)

    @Felix: Haha. Good catch, I’m going to leave it in for good measure. It was obviously accidentally left in from when I was debugging the code and making sure everything worked before I posted it. I would usually add in something of this nature to help fix that problem:

    window.console = console || {
      log: function() { },
      info: function() { },
      debug: function() { }
    };
  5. Mats Lindblad

    Way over my head, as usual :)
    But that’s my loss. I mean, I get functions, you know function name() {} but when you start wrapping stuff in parenthesizes (function() {})(); I’m lost. ;)
    I’ll catch up some day, or maybe switch to being a Project Manager instead, let someone else worry about those things.

    It look good though, and works, so great job.

  6. Tore Darell

    Mats: It’s a way of providing scope so as to not pollute the global scope with your variables, and is often referred to as the module pattern.

  7. Trev

    Very nice work!

    In the _$(els) function for statement, should els.length be arguments.length instead?

  8. Trev

    Ah … nevermind. I just saw the part where window.$ is setup, in which arguments is passed into the _$ function.

    I’ll go try some coffee now :)

  9. RIABG.org » Как да си напишем сами JavaScript библиотека

    [...] В този постинг на Дъстин Диаз е описано накратко, как да започнем наша си собствена библиотека, в която да я има прочутата, магическа функция $. [...]

  10. daniel

    @ mats
    hi
    the (function() {} )(); is very easy
    i learnd it myself yesterday:D befor that, i was lost like you
    but it is very easy

    the 1. bracket calles the function at runtime

    that means that you don’t need to call it on beginning because it is called by itself
    and when you need it later you can then call it….

    hope this is correct :)
    if not, sorry, but thats that, what i understood

    daniel

  11. Federico Feroldi’s blog » Blog Archive » links for 2007-10-04

    [...] Roll out your own JavaScript Interfaces How to implement your own $() (tags: javascript object-oriented oo prototype technique) [...]

  12. daniel

    ok i was false
    it is just called at “runtime”

  13. js fan

    A simple and compact implementation of the jQuery, and more accurate, the version 1.2 the same function wrapper and same method name, great, Cheers, I believe you must like the jQuery very much, and too long no see your podcast, episode 16 is great.

  14. daniel

    if i would now like to “reset” the change on an second click, what would i need to do?

  15. Roll out your own JavaScript Interfaces at thirstymind.org: babblings quenching the thirst of andrew watts’ personal and professional nucleus

    [...] Roll out your own JavaScript Interfaces: This is an article I was thrilled to see. Figuring this out was a TODO in the back of mind. Thankfully Dustin explained it concisely, eloquently and thoroughly. [...]

  16. Uriel Katz

    you don`t need a closure and _$ to make this.
    you can also do a check if this equal to window and if it is create a instance like this:

    
    function $()
    {
      if(this == window)
        return new $.apply(this,arguments);
      //normal init code
    }
    $.prototype=
    {
     //your ptototype here
    };
    

    this technique is used in jQuery so you can use $ instead of new $ or new jQuery every time.

  17. Dustin Diaz

    @Uriel, this technique is ‘not’ used in jQuery. Try testing your code ;) That won’t work. You can’t instantiate something you’re applying. As a matter of fact, jQuery is specifically doing this:

    var jQuery = window.jQuery = function(selector, context) {
    	// If the context is a namespace object, return a new object
    	return this instanceof jQuery ?
    		this.init(selector, context) :
    		new jQuery(selector, context);
    };
    
    // Map over the $ in case of overwrite
    if ( typeof $ != "undefined" )
    	var _$ = $;
    
    // Map the jQuery namespace to the '$' one
    window.$ = jQuery;
  18. Uriel Katz

    my bad,this works:

    
    function $(els)
    {
      if(this == window)
        return new $(els);
      //normal init code
    }
    $.prototype=
    {
     //your ptototype here
    };
    

    this one is tested :)
    what i meant that jQuery use a check to see if jQuery was called without new and if it is it call it self with new.

  19. Michael Geary

    @mats:

    1: foo = function(){}; foo(); // calls foo

    2: foo = function(){}; (foo)(); // calls foo

    3: ( function(){} )(); // calls the anonymous function

    @Dustin: Check your implementation of .css() and .setStyle(). One loop too many?

  20. Ross Harmes

    The “return this;” in the constructor isn’t required. An object is automatically returned when the “new” keyword is used with a function (you can use a return statement in a constructor to override this behavior and pass back an object other than “this”).

  21. Max Design - standards based web design, development and training » Some links for light reading (9/10/07)

    [...] Roll out your own JavaScript Interfaces [...]

  22. Ajaxian » $: Now with more magic!

    [...] Dustin Diaz is on a roll :) He has posted about Roll out your own JavaScript Interfaces in which he discusses the desire to use style from libraries such as prototype, jquery and friends, yet in a small bit of code where you don’t want to use the library: There are times when using a JavaScript library is called for. Building large web applications that use a wide array of utility functions that help aid in developing multi-tiered class systems, advanced UI components, complex event models, and heavy use of DOM scripting helpers. Yep. Those are all great. However, there are other times when you don’t need all that. And often what we end up doing is just importing a few of our favorite functions as globals, and work off those. But what ends up happening in this case is that we lose the particular style that these libraries offer. For instance, I’d still like to be able to do something like this without a library. [...]

  23. David Hopkins

    What are your thoughts on Mootools implementation of the dollar function? It has a bit more bulk to it that all your suggestions, but I am a big fan of Mootools due to its wide array of features; even if many of them are borrowed from prototype etc. I still don’t think anything comes close to its effects engine and I find code produced using Mootools is generally much smaller than other frameworks. Take for example slimbox, which is almost 10 times smaller than the orignal LightBox.

  24. Dustin Diaz

    @Michael Geary: Yeah, I see that. One too many loops for sure as that can be optimized.

    @Ross: I definitely missed and had a near “no duh” moment realizing what you just said. Of course returning that isn’t required, it must have skipped my mind considering that we came up with a new function not too long ago, it should have been obvious.

  25. links for 2007-10-09 « Simply… A User

    [...] Roll out your own JavaScript Interfaces (tags: javascript programming interface library webdev howto design jquery **) [...]

  26. links for 2007-10-09 « Richard@Home

    [...] Roll out your own JavaScript Interfaces jquery in 30 lines. (almost, but a very good introduction to creating chanable methods. (tags: jquery javascript interface library howto) Posted by Richard@Home Filed in 15 [...]

  27. Javascript News » Blog Archive » $: Now with more magic!

    [...] Dustin Diaz is on a roll He has posted about Roll out your own JavaScript Interfaces in which he discusses the desire to use style from libraries such as prototype, jquery and friends, yet in a small bit of code where you don’t want to use the library: There are times when using a JavaScript library is called for. Building large web applications that use a wide array of utility functions that help aid in developing multi-tiered class systems, advanced UI components, complex event models, and heavy use of DOM scripting helpers. Yep. Those are all great. However, there are other times when you don’t need all that. And often what we end up doing is just importing a few of our favorite functions as globals, and work off those. But what ends up happening in this case is that we lose the particular style that these libraries offer. For instance, I’d still like to be able to do something like this without a library. [...]

  28. Daniel W

    Dustin, I tried your procedure for leftover console.* statements:
    window.console = console || {
    log: function() { },
    info: function() { },
    debug: function() { }
    };

    It still gives error, so I made it into:

    window.console = (typeof console == ‘undefined’) ? {
    log: function() { },
    info: function() { },
    debug: function() { }
    } : console;

    Which does not give errors.

  29. Michael Geary

    An easier way to do that is:

    
    window.console = window.console || {
        log: function() { },
        info: function() { },
        debug: function() { }
    };
    
  30. links for 2007-10-10 « [[ the sirens of titan ]]

    [...] Roll out your own JavaScript Interfaces very nice example of a basic modern dom library (tags: code design webdev interface howto) [...]

  31. el_vartauy

    well (postable didn’t work) in short:

    
    // private constructor
    ...
          if (typeof element == 'string')
            element = document.getElementById(element);
    ...
    
  32. weekly linkdump - max - блог разработчиков

    [...] Roll out your own JavaScript Interfaces — как написать мини-jQuery [...]

  33. txt » Chainable interface in JavaScript

    [...] Roll out your own JavaScript Interfaces [...]

  34. Lester

    Very, very nice and a refreshing take on creating libraries, thank you very much.

    However, I would like to have my libraries a little more structured, nested functions come to mind. I tried this:

    
    _$.prototype = {
        (...)
        each: function(...) { (...) }
        Css: {
          setStyle: function(prop, val) {
             (...)
          }
        },
        (...)
    };
    

    Within setStyle(), I haven’t discovered a way how to reach the scope the each() function lies. Can anyone point me in the right direction?

  35. Refresh » Blog Archive » Howto: Styling from scratch

    [...] while back Dustin Diaz did a nice explainer on pieces needed for your own personal JavaScript and AJAX interface — [...]

  36. Ben

    I’m late to the party on this one but was curious how to get this to return false when clicking a link. In the sample code, a div is being clicked but if $(’sample1′, ’sample2′) referred to links and they had actual URLs for graceful degradation instead of just ‘#’ as their href value it would cause the browser to go to that page.

    How do you resolve that?

  37. Strollers Chloe

    I have only just got used to moo tools and thanks for writing this tutorial, i have found it most useful.

    I have been finding javascript so ‘raw’, even such a thing as if($type . .. what a god send !

  38. Professional Web Design Romania | Web Design | Flash Web design

    I have to say I’m lost too :d but it’s good this thing works.
    I’ll have to take a better look and see what I’m missing. Argh…javascript…it’s kind of a must.Not knowing javascript won’t do much good in the future..:(

  39. Chinmay

    This is great. How would you support inheritance and private/public functions with this approach?

  40. Claire Search Engine Optimisation

    I must admit, i only know the basics of javascripts. To send forms and assist with flash ect. Howevever i would like to see in a few years time more understanding of this. Are there any books etc anyone can recommend for this?

    Thanks
    Claire

  41. Jason

    Hey Dustin,

    Is the technique you’re using here have anything to do with Cascades? Some recent reading has lead me to believe the returning of this to allow chainability is also known as Cascades but I’m not sure.

    Thanks,

    Jason

Get "JavaScript Design Patterns"

"As a web developer, you'll already know that JavaScript™ is a powerful language, allowing you to add an impressive array of dynamic functionality to otherwise static web sites. But there is more power waiting to be unlocked--JavaScript is capable of full object-oriented capabilities, and by applying OOP principles, best practices, and design patterns to your code, you can make it more powerful, more efficient, and easier to work with alone or as part of a team."

Buy JS Design Patterns from Amazon.com Buy JS Design Patterns from Apress

All content copyright © 2003 - 2009 under the Creative Commons License.

Archives | Blog Search