The Idea
DomEditor is an app for editing a dominic number list. Dominic numbers are a mnemonic device for memorizing really big numbers. It works by tying every number from 00 through 99 with the image of people doing things. Read more about the Dominic System here if interested. My focus for this article is Javascript/JQuery techniques that helped with this project.
JQuery Event Delegation
Past projects had event handlers all over the place. It was messy. Here is an example from the RESTful notes project.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var deleteButton = makeDeleteButton(rec.id); | |
var changeButton = makeChangeButton(rec.id, rec.title, rec.detail); | |
var rowData = { | |
id: rec.id, | |
title: rec.title, | |
detail: rec.detail | |
}; | |
var rowTemplText = $("#notesRowTempl").html(); | |
var rowTemplate = Handlebars.compile(rowTemplText); | |
var renderedRowText = rowTemplate(rowData); | |
var renderedRowDom = $(renderedRowText); | |
renderedRowDom.find(".deleteButtonCell").append(deleteButton); | |
renderedRowDom.find(".changeButtonCell").append(changeButton); | |
// notesTable.append(row); | |
notesTable.append(renderedRowDom); |
Behind every one of those "make button" functions is an event handler being created. That's a lot of event handlers. Not good. Fortunately, JQuery has this thing called event delegation which you can read about here. With this, you can use just one event handler at a parent level in the DOM. There's no need to register separate handlers for every single button. Here is how DomEditor does it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// delegate event handling example. | |
$(".table").on("click", ".saveButton", saveButtonHandler); | |
var saveButtonHandler = function(evt){ | |
var btn = this; | |
id = btn.getAttribute("id"); | |
// do whatever else that needs be done with respect | |
// this particular button..... | |
}; |
The only catch to that is that DOM navigation is still awkward. My solution was to do some pre-numbering on elements that needed it. This setup gives me fuller use of Handlebars templates. Worth it.
Promises, Closures, and Callbacks
Promises, closures, and callbacks matter because of all the async ajax happening here. This creates a challenge. The server-side API uses basic authentication for user-specific operations. On the client, that means one call needed to get credentials and another call needed to use said credentials. So how do you deal with that without the heavy nesting of callbacks inside of other callback?
Promises
Promises mean less needless callback nesting. A call for credentials gets a promise for that info. From there, you decide what to do with the data. Here's how Dom Editor applies that idea....
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var btnResetHandler = function(evt){ | |
var credPromise = getCreds(); | |
var downloadPromise = credPromise.then(downloadDomSet); | |
downloadPromise.then(updateAllFieldsCallback); | |
}; |
Closure Callbacks
It gets weird trying to save info specific to one dominic number. After all, the button callback only handles credentials. The credentials have to be gotten from another call to begin with. So how do you fit a dominic number argument into the picture?
The solution is to generate a callback. Have it so the dominic number data to already there begin with. Suddenly, argument passing is a non-issue. That is straight up closure action. Here's how that looks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var revertDom = function(num){ | |
var __callback = function(creds){ | |
var uname = creds.username; | |
var pw = creds.password; | |
var authString = "Basic " + btoa(uname + ":" + pw); | |
var req = { | |
url: "/revert/" + num, | |
method: "get", | |
headers: { | |
"Authorization": authString | |
} | |
}; | |
var promise = $.ajax(req); | |
return promise; | |
}; | |
return __callback; | |
}; | |
var credsPromise = getCreds(); | |
var revertDomCB = revertDom(num); | |
var revertDomPromise = credsPromise.then(revertDomCB); | |
revertDomPromise.then(updateDomFields); |
If this is all confusing, take heart. Closures are hard to explain no matter what. Really getting it requires tweaking existing ones and writing some of your own. It should click after awhile.
Final Words
Get hands dirty with these ideas. Write dumb junk pages that use delegate events if you have to. Make promises. Jump into that crazy closure world where functions return other functions. It really is something special once you get these concepts. You'll be a better programmer for it. You'll probably also find better uses for these ideas than memorizing a hundred digits of PI.