Exercise 3 Solution

In notebook:
FrontEndMasters Advanced JavaScript
Created at:
2016-10-07
Updated:
2016-10-07
Tags:
Fundamentals JavaScript jQuery libraries
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:

When attaching a handler function to an element using addEventListener(), the value of this inside the handler is a reference to the element. It is the same as the value of the currentTarget property of the event argument that is passed to the handler.

One solution would be a hard binding (line#8along 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).