Exercise 3 Solution
To convert from the module format to the prototype format
The module format (extract)
// ..
var NotesManager = (function(){
function addNote(note) {
$notes.prepend(
$("<a href='#'></a>")
.addClass("note")
.text(note)
);
}
function addCurrentNote() {
var current_note = $new_note.val();
if (current_note) {
notes.push(current_note);
addNote(current_note);
$new_note.val("");
}
}
function showHelp() {
$help.show();
document.addEventListener("click",function __handler__(evt){
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
document.removeEventListener("click",__handler__,true);
hideHelp();
},true);
}
// ..
})();
The prototype format:
function NotesManager() {
this.notes = [];
}
NotesManager.prototype.addNote = function(note) {
this.$notes.prepend(
$("<a href='#'></a>")
.addClass("note")
.text(note)
);
};
NotesManager.prototype.addCurrentNote = function() {
var current_note = this.$new_note.val();
if (current_note) {
this.notes.push(current_note);
this.addNote(current_note);
this.$new_note.val("");
}
};
NotesManager.prototype.showHelp = function() {
var self = this;
self.$help.show();
document.addEventListener("click",function __handler__(evt){
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
document.removeEventListener("click",__handler__,true);
self.hideHelp();
},true);
};
// ...
NotesManager.prototype.init = function(opts) {
// cache references to the DOM elements we need to manage
this.$notes = $(opts.notes);
this.$new_note = $(opts.new_note);
this.$add_note = $(opts.add_note);
this.$help = $(opts.help);
this.$open_help = $(opts.open_help);
// build the initial list from the existing `notes` data
var html = "";
for (i=0; i<this.notes.length; i++) {
html += "<a href='#' class='note'>" + this.notes[i] + "</a>";
}
this.$notes.html(html);
// listen to "help" button
this.$open_help.bind("click",this.handleOpenHelp.bind(this));
// listen to "add" button
this.$add_note.bind("click",this.handleAddNote.bind(this));
// listen for <enter> in text box
this.$new_note.bind("keypress",this.handleEnter.bind(this));
// listen for clicks outside the notes box
$(document).bind("click",this.handleDocumentClick.bind(this));
// listen for clicks on note elements
this.$notes.on("click",".note",this.handleNoteClick.bind(this));
};
var myNotes = new NotesManager();
// assume this data came from the database
myNotes.loadData([
"This is the first note I've taken!",
"Now is the time for all good men to come to the aid of their country.",
"The quick brown fox jumped over the moon."
]);
$(document).ready(function(){
myNotes.init({
notes: "#notes",
new_note: "#note",
add_note: "#add_note",
help: "#help",
open_help: "#open_help"
});
});
Notes:Don't do this:
NotesManager.prototype = {
addNote: function..
}
This will overwrite the complete prototype
and it might contain some extra objects (apart from the built-in ones)How about some extend
library? (jQuery extend
) - many libraries exist to simplify this tedium (writing NotesManager.prototype.myfunc
every time)showHelp
explained:
NotesManager.prototype.showHelp = function() {
var self = this;
self.$help.show();
document.addEventListener("click",function __handler__(evt){
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
document.removeEventListener("click",__handler__,true);
self.hideHelp();
},true);
};
Both the jQuery and the DOM API will manually bind this
inside the addEventListener
function to the button (DOM element)from MDN docs:
One solution would be a hard binding (line#8When attaching a handler function to an element using
addEventListener()
, the value ofthis
inside the handler is a reference to the element. It is the same as the value of thecurrentTarget
property of the event argument that is passed to the handler.
along with line#7 using this.hideHelp()
):
document.addEventListener("click",function __handler__(evt){
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
document.removeEventListener("click",__handler__,true);
this.hideHelp();
}.bind(this),true);
But this creates the problem that we cannot unbind it anymore (line#6 won't work anymore)So the solution would be here
var self = this
...Generally he advises against this pattern, it's a code smell. You want to do a
this
-style mechanism but you're falling back to lexical-style code.The event handlers is one of the very few exceptions where it's an acceptable solution.
- Which one is better? Module pattern or object delegation?Modular pattern is more useful, but actually neither one, he has a third pattern (in a later slide).