Prerequisites every senior developer must be solid on before the Dojo learning path begins
this BindingA closure is a function that remembers the variables from its outer scope even after that outer function has returned. Dojo uses closures extensively inside AMD module factories — every module you write is a closure.
// Classic closure — the counter pattern
function makeCounter() {
var count = 0; // outer variable
return {
increment: function() { count++; }, // closes over 'count'
value: function() { return count; }
};
}
var c = makeCounter();
c.increment();
c.increment();
console.log(c.value()); // 2
// 'count' is not accessible from outside — it's private
Dojo widget callbacks use closures to capture the widget instance:
// Inside a Dojo widget's postCreate
postCreate: function() {
var self = this; // capture 'this' for use inside callbacks
on(this.domNode, "click", function(e) {
// 'this' here is the DOM event target — NOT the widget
// 'self' is the widget — captured via closure
self.set("selected", true);
});
}
this is not the widget. Always capture it as var self = this in postCreate, or use lang.hitch(this, handler) from dojo/_base/lang.
this Binding RulesJavaScript determines this at call time, not at definition time. Four rules apply, in priority order:
| Rule | How this is set | Dojo Example |
|---|---|---|
| new binding | The new object being created | new MyWidget() |
| Explicit binding | .call(obj) / .apply(obj) / .bind(obj) |
lang.hitch(widget, fn) |
| Implicit binding | The object to the left of the dot | widget.destroy() → this = widget |
| Default binding | window (or undefined in strict mode) |
Bare callback: setTimeout(widget.render, 100) |
require(["dojo/_base/lang"], function(lang) {
var widget = {
label: "Submit",
onClick: function() {
console.log(this.label); // depends on how this is called
}
};
// WRONG — 'this' will be window/undefined
var bad = widget.onClick;
bad(); // → undefined
// CORRECT — hitch binds 'this' permanently to widget
var good = lang.hitch(widget, widget.onClick);
good(); // → "Submit"
});
Dojo's declare() system is built directly on top of JavaScript's prototype chain. Understanding prototypes means you can debug inheritance issues, know when this.inherited() is needed, and understand why widget properties shared across instances can cause bugs.
// JavaScript prototype chain — raw form
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + " makes a noise.";
};
function Dog(name) {
Animal.call(this, name); // call parent constructor
}
// Set up prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return this.name + " barks.";
};
var d = new Dog("Rex");
console.log(d.speak()); // "Rex barks."
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true
Dojo's declare automates exactly this pattern. Understanding the raw form helps you reason about what declare generates under the hood.
declare class body are placed on the prototype, meaning they are shared across all instances. This is a very common source of bugs.
var MyWidget = declare(null, {
// BUG: 'items' is on the prototype — shared by ALL instances
items: [],
addItem: function(item) {
this.items.push(item); // mutates the shared prototype array!
}
});
var w1 = new MyWidget();
var w2 = new MyWidget();
w1.addItem("apple");
console.log(w2.items); // ["apple"] �? BUG! w2 sees w1's item
// FIX: initialize objects/arrays in constructor
var MyWidgetFixed = declare(null, {
constructor: function() {
this.items = []; // each instance gets its own array
},
addItem: function(item) {
this.items.push(item);
}
});
An Immediately Invoked Function Expression (IIFE) creates a private scope to avoid polluting the global namespace. Before AMD, this was the standard way to encapsulate JavaScript modules.
// IIFE pattern — creates isolated scope
(function() {
var privateVar = "I am private";
window.myModule = {
greet: function() { return privateVar; }
};
})(); // �? immediately invoked
console.log(privateVar); // ReferenceError — not accessible
console.log(myModule.greet()); // "I am private"
AMD's define() wraps your module factory in exactly this pattern — but the loader manages the scope for you:
// AMD define — the factory IS an IIFE, managed by the loader
define(["dojo/_base/declare"], function(declare) {
// Everything here is in a private scope
var privateHelper = function(x) { return x * 2; };
// Only what you return is exported
return declare(null, {
double: function(x) { return privateHelper(x); }
});
});
// privateHelper is NOT accessible outside this module
This is why you never see var dojo = ... littering the global scope in properly structured Dojo apps.
arguments Object & Function.prototype.applyDojo's this.inherited(arguments) — the way you call a parent class method — relies entirely on the arguments object. Understanding it is non-negotiable.
function sum() {
// 'arguments' is an array-like object — NOT a real Array
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3)); // 6
// Converting arguments to a real Array (ES5 style)
function toArray() {
return Array.prototype.slice.call(arguments);
}
console.log(toArray(1, 2, 3)); // [1, 2, 3]
this.inherited(arguments) WorksWhen you call this.inherited(arguments) inside a Dojo widget method, you are passing the live arguments object of the current function up to the parent's version of that same method. Dojo uses arguments.callee (in non-strict mode) or its own tracking to identify which method is being called.
var Animal = declare(null, {
speak: function(volume) {
return "noise at " + volume;
}
});
var Dog = declare(Animal, {
speak: function(volume) {
var parentResult = this.inherited(arguments);
// arguments here is [volume] — passed intact to Animal.speak
return parentResult + " (barking)";
}
});
var d = new Dog();
console.log(d.speak("loud")); // "noise at loud (barking)"
this.inherited(arguments) — never this.inherited([arg1, arg2]). The magic is in passing the live arguments object, not a new array.
Dojo's store query results and NodeList collections use the same functional iteration model as ES5 arrays. Knowing these saves you from writing manual for loops everywhere.
var employees = [
{ id: 1, name: "Alice", dept: "Engineering", salary: 95000 },
{ id: 2, name: "Bob", dept: "Marketing", salary: 72000 },
{ id: 3, name: "Carol", dept: "Engineering", salary: 105000 },
{ id: 4, name: "Dave", dept: "Marketing", salary: 68000 }
];
// forEach — iterate with side effects
employees.forEach(function(emp) {
console.log(emp.name);
});
// filter — returns new array matching predicate
var engineers = employees.filter(function(emp) {
return emp.dept === "Engineering";
});
// [ Alice, Carol ]
// map — transform each element
var names = employees.map(function(emp) { return emp.name; });
// ["Alice", "Bob", "Carol", "Dave"]
// reduce — accumulate a value
var totalSalary = employees.reduce(function(acc, emp) {
return acc + emp.salary;
}, 0);
// 340000
// some / every
var anyHighEarner = employees.some(function(e) { return e.salary > 100000; });
// true
// sort (mutates — always work on a copy)
var sorted = employees.slice().sort(function(a, b) {
return a.salary - b.salary; // ascending
});
In Dojo store queries, these same operations appear as store methods:
require(["dojo/store/Memory"], function(Memory) {
var store = new Memory({ data: employees });
// dojo/store filter = array filter
var engResults = store.query({ dept: "Engineering" });
// forEach on results
engResults.forEach(function(emp) {
console.log(emp.name);
});
});
Dojo 1.17 uses its own Deferred class (Phase 5 deep-dive) which predates native Promises. Understanding callback patterns first makes Deferred's design obvious.
// Callback hell — the problem Deferred solves
fetchUser(1, function(user) {
fetchOrders(user.id, function(orders) {
fetchProduct(orders[0].productId, function(product) {
fetchReview(product.id, function(review) {
// 4 levels deep — hard to read, hard to handle errors
render(user, orders, product, review);
}, onError);
}, onError);
}, onError);
}, onError);
// The same flow with Dojo Deferred (covered in Phase 5)
fetchUser(1)
.then(function(user) { return fetchOrders(user.id); })
.then(function(orders) { return fetchProduct(orders[0].productId); })
.then(function(product) { return fetchReview(product.id); })
.then(function(review) { render(review); })
.otherwise(onError); // single error handler for all steps
// Error-first callbacks — pattern used in many Dojo-adjacent libs
function loadData(url, callback) {
// callback signature: function(error, result)
xhr(url, function(err, data) {
if (err) {
callback(err, null); // error in first position
} else {
callback(null, data); // null error means success
}
});
}
loadData("/api/users", function(err, users) {
if (err) { showError(err); return; }
render(users);
});
Dojo has several specific requirements for HTML setup that differ from a typical Bootstrap or React app. Getting these wrong causes silent failures that are frustrating to debug.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Dojo App</title>
<!-- 1. Dijit theme CSS MUST come before dojo.js -->
<link rel="stylesheet" href="dojo/resources/dojo.css">
<link rel="stylesheet" href="dijit/themes/claro/claro.css">
<!-- 2. dojoConfig MUST be set BEFORE dojo.js loads -->
<script>
var dojoConfig = {
async: true, // use AMD loader (always true for 1.7+)
parseOnLoad: false, // ALWAYS false — parse manually
packages: [
{ name: "myapp", location: "/js/myapp" }
]
};
</script>
<!-- 3. dojo.js loaded synchronously in <head> -->
<script src="dojo/dojo.js"></script>
</head>
<!-- 4. Theme class MUST be on <body> — not <html>, not a wrapper div -->
<body class="claro">
<div id="app"></div>
<!-- 5. App bootstrap at end of body -->
<script>
require(["myapp/App", "dojo/domReady!"], function(App) {
new App().placeAt("app").startup();
});
</script>
</body>
</html>
| Requirement | Why it matters |
|---|---|
async: true in dojoConfig |
Enables the AMD loader. Without it, you get legacy synchronous loading — much slower. |
parseOnLoad: false |
Prevents the parser auto-scanning the entire DOM. Always call parser.parse() explicitly. |
| dojoConfig before dojo.js | dojo.js reads dojoConfig at load time. If it's defined after, settings are ignored silently. |
Theme class on <body> |
Dijit CSS selectors start with .claro — must be an ancestor of all widgets. |
| Theme CSS before dojo.js | Prevents FOUC (flash of unstyled content) when widgets render. |
| Theme | Look & Feel | Best For |
|---|---|---|
claro | Clean light blue/gray | Modern professional apps |
tundra | Muted gray tones | Legacy corporate apps |
soria | Blue gradient header bars | Dashboard-style apps |
nihilo | Minimal white | Clean content-focused apps |
Dijit widgets have very specific CSS requirements. Three concepts cause the most friction for developers new to Dijit.
Dijit uses box-sizing: content-box by default (not border-box like Bootstrap). This means width does not include padding or border. Layout widgets calculate sizes precisely — mixing border-box in your own CSS while wrapping Dijit widgets breaks layout math.
/* SAFE — set border-box only on your own elements */
.my-wrapper { box-sizing: border-box; }
/* DANGEROUS — this breaks Dijit layout calculations */
*, *::before, *::after { box-sizing: border-box; }
Dijit theme CSS selectors are intentionally low specificity (e.g., .claro .dijitButton). Override them by prefixing with your container class:
/* Low-risk override — scoped to your container */
.myapp-toolbar .dijitButton {
border-radius: 4px;
font-weight: 600;
}
/* HIGH RISK — overrides ALL dijitButton everywhere */
.dijitButton {
border-radius: 4px;
}
Dijit BorderContainer sets position: absolute on all its child regions. Your container must have an explicit height — height: 100% or height: 100vh. Without it, regions collapse to zero height.
/* Required CSS for a full-page BorderContainer layout */
html, body {
height: 100%;
margin: 0;
overflow: hidden; /* prevent double scrollbars */
}
#appLayout {
height: 100%; /* BorderContainer needs explicit height */
width: 100%;
}
dijitDisplayNone ClassNever use display: none to hide a Dijit widget's container — this causes sizing bugs when the widget is later shown. Use the widget's own API:
// WRONG — hides the DOM node but widget internals stay broken
myWidget.domNode.style.display = "none";
// CORRECT — widget handles visibility properly
myWidget.set("style", "display: none"); // or
require(["dojo/dom-style"], function(domStyle) {
domStyle.set(myWidget.domNode, "display", "none");
});
Dojo registers itself on the global require and dijit objects. These are your primary debugging tools from the browser console.
// List ALL widgets currently alive on the page
dijit.registry.forEach(function(widget) {
console.log(widget.id, widget.declaredClass, widget.domNode);
});
// Find a widget by its DOM node (e.g., from an Elements panel click)
var el = document.querySelector(".myDiv");
var widget = dijit.getEnclosingWidget(el);
console.log(widget);
// Get a widget directly by id
var myDialog = dijit.byId("confirmDialog");
myDialog.show(); // call methods directly from console
// Check if a DOM node has a widget attached
dijit.byNode(document.getElementById("myNode"));
// See which AMD modules have been loaded
Object.keys(require.modules || {}).sort().forEach(function(m) {
console.log(m);
});
// Check if a specific module is loaded (no network request)
require(["dojo/domReady!"], function() {
var mod = require("dijit/form/Select"); // returns cached module
console.log(mod);
});
// Check computed sizes of a BorderContainer's regions
var bc = dijit.byId("mainLayout");
bc.getChildren().forEach(function(child) {
console.log(
child.region,
child.domNode.offsetWidth + "x" + child.domNode.offsetHeight
);
});
// Force a layout recalculation (after dynamic resize)
bc.layout();
// Check if startup() was called (a very common oversight)
var tab = dijit.byId("myTab");
console.log(tab._started); // true if startup() was called
// Track handles on a widget to catch leaks
var MyWidget = declare([_Widget], {
postCreate: function() {
this._handles = []; // always store handles
this._handles.push(
on(this.domNode, "click", lang.hitch(this, "_onClick"))
);
},
destroy: function() {
// Clean up ALL handles before destroying
this._handles.forEach(function(h) { h.remove(); });
this.inherited(arguments);
}
});
Save the following HTML as debug-exercise.html and open it in a browser. It has 5 intentional bugs. Use what you learned in this phase to find and fix all 5.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="dijit/themes/claro/claro.css">
<script src="dojo/dojo.js"></script>
<script>
var dojoConfig = { async: true, parseOnLoad: true };
</script>
</head>
<body>
<div id="container" style="height:300px"></div>
<script>
require(["dijit/layout/BorderContainer",
"dijit/layout/ContentPane",
"dojo/domReady!"],
function(BorderContainer, ContentPane) {
var layout = new BorderContainer({
design: "headline"
}, "container");
var header = new ContentPane({
region: "top",
content: "Header"
});
var center = new ContentPane({
region: "center",
content: "Main content"
});
layout.addChild(header);
layout.addChild(center);
layout.placeAt("container");
// No startup call
var items = []; // widget property defined here
var MyCard = declare(null, {
items: [],
addItem: function(x) { this.items.push(x); }
});
var card1 = new MyCard();
var card2 = new MyCard();
card1.addItem("test");
console.log("card2.items:", card2.items); // expected: []
});
</script>
</body>
</html>
dojoConfig is defined after dojo.js loads — move it before the script tag.parseOnLoad: true — should always be false. Causes double parsing and performance issues.class="claro" on <body> — Dijit widgets will render unstyled.startup() is never called on layout — the BorderContainer renders but regions do not lay out correctly. Add layout.startup(); after placeAt.items: [] on the MyCard prototype is shared between all instances. card2.items shows ["test"] because both cards share the same array. Fix: initialize in constructor: function() { this.items = []; }.
| Concept | Dojo Pattern That Uses It |
|---|---|
| Closures | AMD module factory, widget callback handlers |
this binding | lang.hitch(), widget lifecycle methods |
| Prototype chain | declare() inheritance, mixin chain |
| Prototype trap | Always init objects/arrays in constructor() |
| IIFE | AMD define() module factory |
arguments object | this.inherited(arguments) |
| ES5 array methods | Store query results, NodeList operations |
| Callbacks | Foundation for understanding Deferred |
| HTML boilerplate | dojoConfig before script, theme class on body |
| CSS specificity | Safe Dijit theme overrides |
| DevTools | dijit.registry, getEnclosingWidget |
Time: 6–8 hrs
Pages: 18–22
Exercises: 1
Demo apps: 0