Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I've been using JavaScript for some time now, but mostly just jQuery to prettify web pages. I've started doing some serious development with it in the last year.

So, I want to make sure I'm not doing something stupid. I found a post about the module pattern, and it makes sense to me, so I'm going to attempt to use it on this project instead of keeping nearly everything global.

I'm just a little fuzzy on where I should put event listeners. Is there going to be any issues with scope if the listener is instantiated inside a method? If I'm building something like a toolbar with different functions (kind of like a paint program with 'move', 'select', 'paintbrush', and 'eraser' options), do I keep the onClick for all of those in global scope?

PROJ.namespace("easel");
PROJ.easel.canvas = function() {
  //private variable to store current state
  var current_mode = "select";

  return {
    enableSelect: function () {
      //disable other tools, enable 'select' event listeners
    }
    enablePaintbrush: function () {
      //disable other tools, enable 'paintbrush' event listeners
    }
    enableMove: function () {
      //disable other tools, enable 'move' event listeners
    }
    enableEraser: function () {
      //disable other tools, enable 'eraser' event listeners
    }
  };
}();

//event listeners for the tool pallete
$('#tools #paintbrush').on('click', function() { PROJ.easel.canvas.enablePaintbrush() };
$('#tools #move').on('click', function() { PROJ.easel.canvas.enableMove() };
$('#tools #select').on('click', function() { PROJ.easel.canvas.enableSelect() };
$('#tools #eraser').on('click', function() { PROJ.easel.canvas.enableEraser() };

Of course, once the specific tool is selected, it needs its own listeners. For example, a paintbrush needs handlers for 'mousedown' (start painting), 'mousemove' (lay down paint), and 'mouseup' (stop painting). Can those listeners be enabled inside of PROJ.easel.canvas?

EDIT: How are you supposed to pick an "answer" on codereview.stackexchange.com? You're asking peoples' opinions...

share|improve this question
    
I strongly suggest reading addyosmani.com/resources/essentialjsdesignpatterns/book/… it's a great resource on JS design patterns, it's free and it describes how to solve your issue in length –  Benjamin Gruenbaum Feb 14 '13 at 21:13
    
This looks like a good resource, I'll start reading it. –  coding_hero Feb 14 '13 at 21:28

2 Answers 2

up vote 3 down vote accepted

The way I like to use the module partern is in combination with the Oberserver pattern. I've included detailed comments in the code to help explain the two a bit. I would highly recomend you watch this video by Jeffery Way. He does a really nice job of explaining the two and also some custom events, which I think you might be interested in. Also read the links he puts under the video, they'll provide you with a complete understanding of it all.

I explain most of it in the code with comments. Anyways the following code will help you get the picture:

(function ( $, window, document, undefined ) {
   "use strict";

    var PROJ = {
        init: function() {
            //Set up my Observer Pattern
            var o = $( {} );
            $.subscribe = o.on.bind(o);
            $.unsubscribe = o.off.bind(o);
            $.publish = o.trigger.bind(o);

            //By using the object PROJ you can reference it with "this" (i.e. this.whatever();), easy peazy "namespacing"
            //Example:
            this.setUpListeners();
        },

        setUpListeners: function() {
            $('#tools #paintbrush').on('click', function() { PROJ.enablePaintbrush() };
            $('#tools #move').on('click', function() { PROJ.enableMove() };
            $('#tools #select').on('click', function() { PROJ.enableSelect() };
            $('#tools #eraser').on('click', function() { PROJ.enableEraser() };

            //Here is where the Observer Pattern kicks in nicely
            //I'm done with the listeners, and I'm letting everyone that is subscribed know that.
            //You can also namespace easily with "/" like so: ("enable/Select") or ("enable/Move") etc.
            $.publish( "listeners/are/set/up" );
        },

        enableSelect: function() {
           //do some stuff

           //I'm done with "enable", and I'm letting everyone that is subscribed know that.
           $.publish( "enable" );
        },

        enablePaint: function() {
           //do more stuff

           //When enable is published, run enableMove
           $.subscribe( "enable", this.enableMove( //You can pass arguments here to other functions, limiting the scope );
        },

        enableMove: function() {
            //In a callback "this" refers to the element in question, and not to the PROJ object

            //You can cache it out here:
            var self = this

            $("#something").on( "click", function() {
                //To resolve that you can also cache the reference in here
                var self = PROJ

                //this will work
                self.enableMove();

                //this will not work because we are in a callback
                this.enableMove();

                //You can use your Pub/Sub model anywhere, even in callbacks
                $.publish( "done/with/this/baby" );
            });
        },

        enableEraser: function() {
            $.subscribe( "done/with/this/baby", this.enableSelect() );
            //Just like that I've created a loop, by calling enableSelect it goes back and runs it again.
        } //The last closing braquet doesn't have a comma
    }; //Close the PROJ object

    //Initialize the whole thing. Can be referenced anywhere in your code after it has been declared.
    PROJ.init();
})( jQuery, window, document );

Hopefully that provided some insight. If there's any part which needs clarification I'll be happy to explain.

share|improve this answer
    
Your example and the video were very helpful. I can more clearly see how this can work now. I knew about custom listeners, but hadn't made the leap to the pub/sub model yet. This was very helpful. I'll go through the links as well now. –  coding_hero Feb 15 '13 at 19:32
    
What would member variables look like? –  coding_hero Feb 15 '13 at 21:07
    
$.subscribe( 'name', function() ); should not be $.subscribe( "name", function ); ? –  JCM Feb 16 '13 at 20:49
    
Doesn't matter if you use "" or '' they both have the same effect. Usually '' is used inside "" for example: $("div:contains('John')") –  Jonny Sooter Feb 17 '13 at 4:46
    
@coding_hero does this answer your question? googleappsdeveloper.blogspot.com/2010/06/… –  Jonny Sooter Feb 17 '13 at 4:52
  • Deep Namespacing Pitfalls

    JavaScript was not designed to handle very deep namespaces and does not have built-in ways to handle such. And further more, JS is NOT your typical, classical OOP language like Java, C++ and C# so better avoid such conventions.

    Also, I forgot if I read this from a JS book or got it from a video from YUI, accessing object properties incurs a performance penalty (although minute, but still an overhead). Since the practice you have shown is basically nested objects, where properties hold objects, then it will cost you.

    As a general tip, you can do namespacing but avoid deep namespaces.

  • Selecting IDs

    IDs are supposed to be unique elements in the page. Thus, specificity should not be an issue here and so you can directly just select an ID by using the ID without any other selector.

    //So
    $('#tools #eraser')
    //can just be 
    $('#eraser')
    
  • "return" module pattern vs "append-to-namespace" module pattern

    Well, I just made up those pattern names, but it would be easier to call them that way.

    The return module pattern has issues. For one, it completely overrides/erases an existing namespace by assigning another object to it. Secondly, having a return inside the module is pretty messy. Forgetting a comma is one such problem.

    Thus, the "append-to-namespace" pattern is better in some ways and is better explained in the actual code:

    //the this.namespace given below is provided into this block as ns
    (function(ns){
    
      //you can check ns for stuff appended prior to this block's execution
      //this can be seen in some libraries like RequireJS for pre-config parameters
      if(ns.somethingAttachedBefore){...}
    
      //also, instead of returning stuff to the namespace, you can append to it
      //effectively avoids object literal lists and forgotten commas
      ns.something = function(){...}
    
    
    //this line assigns an existing namespace or a new object to the given namespace
    }(this.namespace = this.namespace || {}));
    
share|improve this answer
    
I'll admit that I don't fully understand the syntax here. How is this whole thing enclosed in ()'s? Are they interchangeable with {} in JS? –  coding_hero Feb 15 '13 at 17:53
    
By the way, good info, thanks. I've often brought up the "ID's are singular" issue with many designers, so I know it well. I'm just more careful than warranted in this case. Probably because I've often had to code JS for a designer who used the same ID multiple times. :) –  coding_hero Feb 15 '13 at 18:07
    
Aha, nevermind. Self-invoking anonymous function. I'm sure I knew about those at one point in time... –  coding_hero Feb 15 '13 at 19:02

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.