-
Notifications
You must be signed in to change notification settings - Fork 37
Live extensions
There are only two methods to deal with: DOM.extend
and DOM.mock
. The second method is used for testing. In other words to get your task done you need to understand how DOM.extend
works.
DOM.extend
declares a live extension. It accepts a CSS selector as the first argument that defines what elements you want to capture.
General advice: try to make the selector simpler. Ideally you should only use a tag name, class or attribute with or without a value or their combinations with each other. These selectors can be tested quicker without calling an expensive Element#matches
method.
The second argument is a live extension definition. All properties of the object will be mixed with an element wrapper interface. Except private functions, that are removed after constructor
ends.
Let’s look at a simple example. Assuming we have an element like below on a web page:
<div class="signin-form modal-dlg">...</div>
The task is to show it as a modal dialog which should work for any existing and future content. This is how the live extension could look like:
DOM.extend(".modal-dlg", {
constructor: function() {
var backdrop = DOM.create("div.modal-dlg-backdrop");
// using bind to store reference to backdrop internally
this.showModal = this.showModal.bind(this, backdrop);
// append backdrop to the DOM
DOM.find("body").append(backdrop);
// we will define event handlers later
},
showModal: function(backdrop) {
this.show();
backdrop.show();
}
});
Constructor is usually the place where you attach event handlers and perform DOM mutations where necessary.
General advice is do not rely on a moment of time when an extension is initialized. The actual initialization of a live extension varies across browsers.
Let’s update our .signin-form
live extension to handle click on a close button and ESC
key:
DOM.extend(".modal-dlg", {
constructor: function() {
var backdrop = DOM.create("div.modal-dlg-backdrop"),
closeBtn = this.find(".close-btn");
this.showModal = this.showModal.bind(this, backdrop);
// handle click on the close button and ESC key
closeBtn.on("click", this.onClose.bind(this, backdrop));
DOM.on("keydown", this.onKeyDown.bind(this, closeBtn), ["which"])
},
showModal: function(backdrop) {
this.show();
backdrop.show();
},
onClose: function(backdrop) {
this.hide();
frame.hide();
},
onKeyDown: function(closeBtn, which) {
if (which === 27) {
// close dialog by triggering click event
closeBtn.fire("click");
}
}
});
showModal
above is a public method. You can access it in any (present or future) element that has the modal-dlg
class:
var signinForm = DOM.find(".signin-form");
DOM.find(".signin-btn").on("click", function() {
signinForm.showModal(); // => shows the signin dialog
});
In a live extension definition any function that starts with on
or do
plus an upper case letter are called private function. Such function have a special behaviour: when the constructor
is finished all such members are removed from the interface. Also, starting from better-dom 2 it preserves this
always to be the current element.
In our case despite the live extension definition contains methods onClose
and onKeyDown
they won’t be mixed into the element wrapper interface after constructor
ends:
var signinForm = DOM.find(".signin-form");
console.log(signinForm.onClose); // => undefined
console.log(signinForm.onKeyDown); // => undefined
On some projects it’s useful to extend all element wrappers with a particular method(s). You can use the universal selector to solve the problem:
DOM.extend("*", {
gesture: function(type, handler) {
// implement gestures support
}
});
…
DOM.find("body").gesture("swipe", function() {
// handle a swipe gesture on body
});
The "*"
selector has special behavior: all extension declaration properties will be injected directly into the element wrapper prototype except the constructor
which is totally ignored. So there is no performance penalty that is usually associated with the universal selector.
Obviously it doesn't make sense to put private functions there.
*Never pass more specific selectors like ".some-class " into DOM.extend
because they do not have the behavior above and therefore they are slow.
Sometimes it makes sense to split a large live extension into several pieces to reduce complexity. For instance you may have an element like below on your page:
<div class="infinite-scroll chat"></div>
There are two different extensions attached to it. The .infinite-scroll
extension implements a well-known infinite scroll pattern, e.g. it’s responsible for loading a new content. The .chat
extension shows tooltips when a user hovers over a userpic, adds smiles into messages etc.
But be accurate with multiple extensions: despite all event handlers are removed from interface you still might have public methods that intersect with each other.