principlejavascriptMinor
JavaScript event handlers, scope, and the module design pattern
Viewed 0 times
thehandlersjavascriptdesignmodulescopeandpatternevent
Problem
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?
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 "a
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 "a
Solution
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:
Hopefully that provided some insight. If there's any part which needs clarification I'll be happy to explain.
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.
Code Snippets
(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 );Context
StackExchange Code Review Q#22730, answer score: 5
Revisions (0)
No revisions yet.