Reputation: 11157
I have a problem, which is not easily described. I'm writing a web application that makes strong usage of jQuery and AJAX calls. Now I don't have a lot of experience in Javascript archicture, but I realize that my program has not a good structure. I think I have too many identifiers referring to the same (at least more or less) thing.
Let's have an look at an arbitrary exemplary UI widget that makes up a tiny part of the application: The widget may be a part of a window and the window may be a part of a window manager:
Now I have somehow four different object identifiers for the same thing, which needs to be kept in sync until the page is loaded anew. This seems not to be a good thing.
EDIT2:
I think it would helpful to make an example, albeit an artificial one. Below you see a logger widget that can be used to add a block element to a web page in which logged items are displayed.
makeLogger = function(){
var rootEl = document.createElement('div');
rootEl.innerHTML = 'Logged items:';
rootEl.setAttribute('class', 'logger');
var append = function(msg){
// append msg as a child of root element.
var msgEl = document.createElement('div');
msgEl.innerHTML = msg;
rootEl.appendChild(msgEl);
};
return {
getRootEl: function() {return rootEl;},
log : function(msg) {append(msg);}
};
};
// Usage
var logger = makeLogger();
var foo = document.getElementById('foo');
foo.appendChild(logger.getRootEl());
logger.log('What\'s up?');
At this point I have a wrapper around the HTMLDivElement (the hosted object). With having the logger instance (the native object) at hand I can easily work with it through the function logger.getRootEl().
Where I get stuck is when I only have the DOM element at hand and need to do something with the public API returned by function makeLogger (e.g. in event handlers). And this is where the mess starts. I need to hold all the native objects in a repository or something so that I can retrieve again. It would be so much nicer to have a connection (e.g. a object property) from the hosted object back to my native object.
I know it can be done, but it has some drawbacks:
For now, I do the back referencing with jQuery's data() method. But all in all I don't like the way I have to keep track of the relation between the hosted object and its native counterpart.
Upvotes: 27
Views: 3552
Reputation: 81
A lot of what you can or can't do will depend on how much control you have over the javascript. Personally I often have to use libraries built by others so I might only get a DOM node to work with, but I really need my object instead. In these cases I find using the data feature in jQuery is very handy. By using the data feature, you can 'store' your object inside the DOM node to retrieve it later.
Given your example above, here's how you could use the data feature to get back your widget after having functions that only use the DOM node.
makeLogger = function(){
var rootEl = document.createElement('div');
rootEl.innerHTML = 'Logged items:';
rootEl.setAttribute('class', 'logger');
var append = function(msg){
// append msg as a child of root element.
var msgEl = document.createElement('div');
msgEl.innerHTML = msg;
rootEl.appendChild(msgEl);
};
var self = {
getRootEl: function() {return rootEl;},
log : function(msg) {append(msg);}
};
// Save a copy to the domNode
$(rootEl).data("logger", self);
return self;
};
// Example of only getting the dom node
function whatsUp (domNode){
// Get the logger from the domNode
$(domNode).data('logger').log('What\'s up?');
}
// Usage
var logger = makeLogger();
var loggerNode = logger.getRootEl();
var foo = document.getElementById('foo');
foo.appendChild(loggerNode);
whatsUp(loggerNode);
Upvotes: 0
Reputation: 141909
For the four objects that need to be synchronized, you could have a single object and pass the reference around in a constructor, or as function arguments.
The way I fix this problem is to never lose a reference to the wrapper object. Whenever a DOM object is needed (for example inserting into the page), this wrapper object provides it. But sticking that widget onto the screen, the wrapper object sets up all event handling and AJAX handling code specific to the widget, so the reference the the wrapper is maintained at all times in these event handlers and AJAX callbacks.
I've created a simple example on jsfiddle using MooTools that might make sense to you.
Upvotes: 2
Reputation: 8011
I'm not sure I've fully understood your question, but I'll try to point some ideas.
In my opinion, you should make base widget class, which contains common functionality for widgets.
Let's use for example AppName.Widgets.base(). One of the instance variables is _events, which is object that stores events as keys and function as values. That way each class defines the events for this widget, and you can easily bind them in the constructor. As for the string identifiers, the easiest way is to use toString().
Example:
namespace('AppName.Widgets'); // you can find implementations easy
AppName.Widgets.base = function() {
if (!this._type) return;
this._dom = $('div.widget.'+this._type);
for (var e in this._events) {
this._dom.bind(e, this._events[e]);
}
this.toString = function() { return this._type; };
}
AppName.Widgets.example = function() { // extends AppName.Widgets.base
this._type = 'example';
this._events = { 'click' : function(e) { alert('click'); } };
AppName.Widgets.base.call(this);
}
Upvotes: 1
Reputation: 82814
You could use a global registry:
window.WidgetRegistry = {};
window.WidgetRegistry['foowidget'] = new Widget('#myID');
and when AJAX calls return, they can get the widget like this:
var widgetID = data.widgetID;
if (widgetID in window.WidgetRegistry) {
var widget = window.WidgetRegistry[widgetID];
}
For your jQuery calls: I'd guess they are relatively inexpensive, since jQuery caches objects for later use. But you could extend the above suggested WidgetRegistry
by using .data()
:
var $widget = $('#myWidget');
var widgetID = 'foo';
$widget.data('widget', widgetID);
In this way, you can store the widget ID attached to each jQuery object and re-access it from the global registry.
Testing, if an jQuery object has an existing widget:
return $('#test').data('widget') &&
($('#test').data('widget') in window.WidgetRegistry);
Note, that these are just suggestions. Actually, there are dozens of ways to achieve a consolidation like this. If you want to combine your code deeper with jQuery, you could extend the jQuery object, so that you could write something like:
$('#widget').widget({'foo':'bar'});
// and/or
var allWidgets = $('*:widget');
// ...
Upvotes: 5