I've noticed a common pattern in the JavaScript I've been writing and was wondering if there is already a pattern out there that defines something similar as best practice? Essentially, it's how to get a DOM element and wrap it inside / associate it with a JavaScript object. Take this example, where you need a filter in your web app. Your page looks like this:
<html>
<head></head>
<body>
<div id="filter"></div>
</body>
</html>
You'd then wrap the element like so:
var myFilter = new Filter({
elem: document.getElementById('filter'),
prop: 'stacks-test',
someCallback: function() {
// specify a callback
}
});
And the JavaScript (where spec is an object passed to the constructor):
var Filter = function(spec) {
this.elem = spec.elem;
this.prop = spec.prop;
this.callback = spec.someCallback;
this.bindEvents();
};
Filter.prototype.bindEvents = function() {
var self = this;
$(this.elem).click(function(e) {
self.updateFeed();
};
};
Filter.prototype.updateFeed = function() {
this.prop; // 'stacks-test'
this.callback();
// ...
// code to interact with other JavaScript objects
// who in turn, update the document
};
What is this kind of approach called, and what are the best practices and caveats?
You might be interested in Dojo's widget library, Dijit - if I'm understanding your question correctly, it essentially does what you're asking, and a whole lot more.
In Dijit, a widget essentially encapsulates a DOM node, its contents, any JavaScript that defines its behavior, and (imported separately) CSS to style its appearance.
Widgets have their own lifecycle, registry, and events (including many which simply map to DOM events on a node within the widget, e.g. myWidget.onClick could effectively call myWidget.domNode.onclick).
Widgets can (but don't have to) have their initial contents defined in a separate HTML template file, through which it's also possible to bind events on nodes within the template to widget methods, as well as set properties on the widget that reference particular nodes in the template.
I'm barely scratching the surface here. If you want to read more on this, you can start with these reference pages:
http://dojotoolkit.org/reference-guide/dijit/info.html
http://dojotoolkit.org/reference-guide/dijit/_Widget.html (the base that all widgets extend)
http://dojotoolkit.org/reference-guide/dijit/_Templated.html (RE the HTML templating)
http://dojotoolkit.org/reference-guide/quickstart/writingWidgets.html (useful information when starting to write your own widgets)
http://dojotoolkit.org/reference-guide/dijit/ (for a bunch more info)
All said, I don't know what you're ultimately aiming for, and maybe this is all a bit much for your purposes (considering I'm throwing an entire other library at you), but figured it might pique your interest at least.
Continuing from my comment on the question, jQuery is a potential tool for the job, as it already provides some of the foundations for what you're after. However, having said that, it does introduce complexities of its own, and further, not all "jQuery ways" are equal. I'll suggest one way of using jQuery as your "object model", but it may or may not suit your needs.
First things first. The philosophy of jQuery is that you start everything by selecting the element first, using $(), or equivalently jQuery(). All operations conceptually begin with this. This is a slightly different way of thinking compared to creating an object that wraps an element and keeping a reference to that wrapper, but essentially this is what jQuery does for you. A call to $('#some-id') grabs the element with id of "some-id" and wraps it in a jQuery object.
One way: Write "Filter" plugins.
Replace your constructor with a initFilter() jQuery method. You can do this by modifying the jQuery prototype and using the jQuery object as your wrapper. jQuery's prototype is referenced by jQuery.fn, so:
jQuery.fn.initFilter = function (prop, callback) {
// Save prop and callback
this.data('filter-prop', prop);
this.data('filter-callback', callback);
// Bind events (makes sense to do this during init)
this.click(function () {
$(this).updateFeed();
});
};
Then do a similar thing for updateFeed():
jQuery.fn.updateFeed = function () {
this.data('filter-prop');
this.data('filter-callback')();
});
And use it like this:
$('#filter').initFilter(prop, callback);
Note that updateFeed can simply be in-lined into the click handler to prevent unnecessary pollution of the jQuery namespace. However, one advantage of using jQuery like this is that you do not need to keep a reference to the object if you need to invoke some function on it later, since jQuery ties all references to actual elements. If you'd like to call updateFeed programmatically, then:
$('#filter').updateFeed();
will then be invoked on the correct object.
Some things to consider
There are certainly downsides to this method. One is that all properties, which we've saved against the element using .data(), are shared between all jQuery functions that act on that element. I've attempted to alleviate this by prefixing the property names with "filter-", but depending on the complexity of your object(s), this may not be suitable.
Further, this exact method may not be so suitable for objects that require a lot of manipulation (i.e. objects with many functions) since all of these functions become common to all jQuery objects. There are ways to encapsulate all this which I won't go into here, but jQuery-ui does this with their widgets, and I'm experimenting with yet another alternative in a library I'm creating.
However, pulling back a bit, the only reason I suggested using jQuery in the first place is that your Filter object appears to be heavily tied to the DOM. It binds events to the DOM, it modifies the DOM based on user interaction, basically it appears to live in the DOM, so use something DOM-based, i.e. jQuery.
Related
I want to make an UI object that saves references to DOM objects and also some UI strings, so I can use it all over my code and can handle name changes easily. Kind of like this:
var UI = {
DOMelem0: document.getElementById('imUnique'),
DOMelem1: document.getElementById('imSpecial')
};
However, I think that everytime I would access DOMelem0 (by calling UI.DOMelem0), for instance, I'd be calling the getElementById() function, is that what happens?
Or is it no different than storing the elem in a scoped variable? (Like so: var elem = document.getElementById('cow');)
I'm worried about any performance issues this might cause if I were to have lots of UI elements, although I guess they'd be minimal. Either way, I wouldn't want to be calling the DOM method all the time.
Thanks.
Calling UI.DOMelem0; will not call document.getElementById('imUnique').
document.getElementById('imUnique') is only called when you first create the UI object.
I maintain a custom library consisting of many dijit widgets at the company I work at.
Many of the defects/bugs I have had to deal with were the result of this.inherited(arguments) calls missing from overriden methods such as destroy startup and postCreate.
Some of these go unnoticed easily and are not always discovered until much later.
I suspect I can use dojo\aspect.after to hook onto the 'base' implementation, but I am not sure how to acquire a handle to the _widgetBase method itself.
Merely using .after on the method of my own widget would be pointless, since that wouldn't check whether this.inherited(..) was inded called.
How can I write a generic test function that can be passed any dijit/_WidgetBase instance and checks whether the _widgetBase's methods mentioned above are called from the widget when the same method is called on the subclassing widget itself?
Bottom-line is how do I acquire a reference to the base-implementation of the functions mentioned above?
After reading through dojo's documentation, declare.js code, debugging, googling, debugging and hacking I end up with this piece of code to acquire a handle to a base method of the last inherited class/mix-in, but I am not entirely happy with the hackiness involved in calling getInherited:
Edit 2 I substituted the second param of getInherited with an empty array. While I actually get a reference to the method of the baseclass using aspect doesn't work. It appears this approach is a bust.
require(['dijit/registry','dojo/_base/declare','mycompany/widgets/widgetToTest'],
function(registry,declare,widgetToTest)
{
var widget = registry.byId('widgetToTestId');
var baseStartup = getBaseMethod(widget,'startup');
function getBaseMethod(widget,methodName){
return widget.getInherited(methodName,[]);
}
//This is the method body I want to use .after on to see if it was called, it returns the last overriden class in the array of inherited classes. (a mixin in this case, good enough for me!)
alert(baseStartup);
});
I have given up trying to use dojo/aspect.
I have instead opted to modify the code of our custom base widget to incorporate snippets such as the one below. They are automatically removed when creating a release-build in which console-calls and their content are removed:
console.log(
function(){
(this._debugInfo = this._debugInfo|| {}).postCreate=true;
}.call(this)
);
A simple method in boilerplate code I added near the unittests is available so that I can call it on all mycompany.widgets.basewidget instances in their respective unittests.
So I have a set of jQuery plugins, really basic stuff, but I split the code into plugins because I don't like having a huge jQuery(document).ready() function where I store the entire application logic.
Each plugin has a "destructor", which is basically a function that I defined in the plugin prototype object. This function unbinds events used by the plugin, removes DOM elements that were added by the plugin etc.
Plugins are initialized like this:
$('.element').plugin();
Is there any way I can get all the elements that have my plugins attached to them, from another plugin which is supposed to replace the body HTML, so I can call the destructor function?
I was thinking to store each plugin instance inside a global array, then I can access that array from any plugin. But maybe there is a better way that doesn't use the global state?
I don't think there is a ready made method for it... but as a hack you can add a class to the target elements in your plugin and then use that class to get all elements with the widget initialized lke
$.fn.plugin = function(){
this.addClass('my-plugin-class');
}
then to initialize
$(element).plugin()
to get all elements with the plugin
$('.my-plugin-class')....
But if it is a jQuery UI widget then you can use the selector $(':ui-widgetname'), see this answer
Arun P Johny wrote the rigth idea -- just delete 'footprint' of your job by marking the affected DOM elements with some specific class name.
I want just add an idea. Plugins are the methods of the library and nothing more. If you need the destroyer for constructor -- just make another plugin for it:
$.fn.overture = function (){...};// construct
$.fn.crescendo = function (){...};// more construct
$.fn.quietFarewell = function (){...};// destructor for everything above
$(...).overture().crescendo().quietFarewell();
What's the difference between:
$(this.el).html
and
this.$el.html
Reading a few backbone examples and some do it one way and other another way.
$(this.el) wraps an element with jQuery (or Zepto). So, if your view HTML was this:
<div id="myViewElement"></div>
...and this.el referenced that div, then $(this.el) would be the equivalent of retrieving it directly via jQuery: $('#myViewElement').
this.$el is a cached reference to the jQuery (or Zepto) object, so a copy of what you would get from calling $(this.el). The intent is to save you the need to call $(this.el), which may have some overhead and therefor performance concerns.
Please note: the two are NOT equivalent. this.el alone is a reference to a host object HTMLElement -- no libraries involved. This is the return of document.getElementById. $(this.el) creates a new instance of the jQuery/Zepto object. this.$el references a single instance of the former object. It is not "wrong" to use any of them, as long as you understand the costs of multiple calls to $(this.el).
In code:
this.ele = document.getElementById('myViewElement');
this.$ele = $('#myViewElement');
$('#myViewElement') == $(this.ele);
Also, it is worth mentioning that jQuery and Zepto have partial internal caches, so extra calls to $(this.el) might end up returning a cached result anyway, and that's why I say "may have performance concerns". It also may not.
Documentation
view.$el - http://backbonejs.org/#View-$el
$ in backbone - http://backbonejs.org/#View-dollar
jQuery base object - http://api.jquery.com/jQuery/
Zepto base object - http://zeptojs.com/#$()
The two are essentially* equivalent, with $el being a cached version of the jQuery or Zepto objects el, the reason why you see examples using $(this.el) is because it was only added in a later release of backbone.js (0.9.0).
*Technically as Chris Baker points out $(this.el) will (probably) create a new jQuery/Zepto object each time you call it while this.$el will reference the same one each time.
If $el exists on this and is a jQuery object, you shouldn't use $(this.el) because it would be initializing a new jQuery object when one already exists.
They yield exactly the same thing; that is, a reference to a view's element. $el is simply a jquery wrapper for $(this.el). Look at this reference: http://documentcloud.github.com/backbone/#View-$el
I usually see this:
var markup = $(this).html();
$(this).html('<strong>whoo hoo</strong>');
I agree with Raminon. Your examples you've seen look wrong.
This code is typically seen within a jquery loop, such as each(), or an event handler. Inside the loop, the 'el' variable will point to the pure element, not a jQuery object. The same holds true for 'this' inside an event handler.
When you see the following: $(el) or $(this), the author is getting a jQuery reference to the dom object.
Here's an example I just used to convert numbers to roman numerials:
(Note, I always use jQuery instead of $ -- too many collisions with mootools...)
jQuery(document).ready(function(){
jQuery('.rom_num').each(function(idx,el){
var span = jQuery(el);
span.html(toRoman(span.text()));
});
});
Wrapping an element in $() appends the jQuery extensions to the object prototype. Once that's done it doesn't need to be done again, although there's no harm other than performance in doing it multiple times.
HI Guys, I have a question,
Im trying fo find the most efficient way in terms of performance to store and access an element in the javascript protoype library.
lets say I dynamically create a parent with an child element in a test class
testclass = Class.create({
newParent: null, //I will create a global reference to the parent element
method1: function(){
this.newParent = new Element('div',{'id':'newParent'});
var elm = new Element('div',
{
'id': 'elm1',
'identifier': 'elm1identifier'
}
);
newParent.insert(elm);
},
method2: function(){
??????????
}
})
In method 2, I want to be able to access the element elm1.
I have been thinking, and here are my different solution.
I can access the element using the utility method provided by prototype $()
method2: function(){
$('elm1');
}
I can make a global reference to the element.
elm1: null,
....
method2: function(){
this.elm1
}
3.I can pass the element in the method as a parameter but this option will not always be available
I create a unique identifier as an attribute and use the protoype .down function
this.newParent.down('[identifier=elm1identifier]');
So ofcourse i use a combination of these, but im curious, at out of all the methods, which is the most efficient in terms of performance.
I heard that the $() utility method searches the whole dom? Is this a significant difference? What if you have alot of elements.
Storing references to elements may also cause memory problems especially if you have a lot of javascript in a big web site.
using a unique and custom identifier is also nice, but you also add new attributes which might have an effect on the dom itself. But this advantage is that you specifiy where you want to search using the element.down() method in prototype.
Thanks for the help guys.
If you are creating an object that is supposed to represent a DOM element, it seems sensible to give it a property that references the element, say "element". Then in method1:
this.element = elm;
I think at this stage, micro-optimising for performance is pointless since the biggest improvement in performance would be to not use Prototype.js.
The $() method is firstly an alias for document.getElementById (which is blazingly fast in browsers and always has been) and secondly adds convenience methods to the element returned. If the browser doesn't implement a prototype based inheritance scheme, Prototpye.js adds about 50 methods directly to the element as properties, which is a pretty expensive operation.
I doubt that storing references to elements will cause significant memory issues unless you are storing tens of thousands and not using them (they are just references after all).
using a unique and custom identifier
is also nice, but you also add new
attributes which might have an effect
on the dom itself
Do not add custom attributes or properties to DOM elements. Prototype.js version 2.0 is going away from this model (at last), just don't do it. If you have an object that represents (or "wraps") an element, add the property to that.