I was recently fussing over how developers don’t tend to separate their DOM logic/interfaces from their JavaScript “business logic”. Fuss as I might, I left the tirade rather open-ended, and that’s because I wasn’t sure that it was really a bad thing.
Either way, since JavaScript and the DOM are bunking together, we may as well make it safe — OOP safe.
For fun, let’s see how this might look:
/* * Word * A class that encapsulates a word */ function Word(word) { this.setDOM('<li></li>'); this.text(word).css('color', 'red'); // jQuery methods!!! } Word.prototype = new jQuery.Interface; // Magical |
So, we’re sub-classing something called jQuery.Interface
, and jQuery.Interface
is sub-classed from jQuery itself, meaning that you get all of jQuery’s methods in your object.
This is nothing ground-breaking because we always knew we could do stuff like:
function Planet() { // Implement planet logic } Planet.prototype = jQuery([]); // Planet now inherits jQuery's methods!! |
But… inheriting straight from jQuery()
won’t give you what you want. Firstly, you’ll need to mess around with populating the DOM references yourself (this[0] = jQuery('<div/>')[0]
etc.), and you’d have to manage the length
property too, and lastly you wouldn’t get all of the jQuery chainability that you’re used to. Not as you might expect it, at least:
var earth = new Planet; earth.age = 4540000000; earth.appendTo('body').age; // undefined |
It’s undefined
because jQuery’s DOM manipulation methods will return new jQuery objects, not simply a reference to this
, which is what we’d really like!
So, to make it a little easier, I created a little thing called jQuery.Interface
which does these things for you, enabling you to have beautiful jQuery sub classes.
Subclassing jQuery means that your class’ instances can be treated just like jQuery instances. Here’s a more realistic potential application of it:
/** * HexColorTooltip * Shows a colorised tooltip when hovering over HEX colors */ function HexColorTooltip() { this.offsetTop = 10; this.offsetLeft = 10; this.setDOM('<div/>', { css: { position: 'absolute', border: '1px solid black', height: 50, width: 50, zIndex: 100 } }); $('body').bind('mousemove', $.proxy(function(e){ this.mouseX = e.pageX; this.mouseY = e.pageY; }, this)); this.appendTo('body').hide(); } HexColorTooltip.colorRegex = /^(?:#[A-F0-9]{3}|#[A-F0-9]{6})$/i; HexColorTooltip.prototype = $.extend(new $.Interface, { bindTo: function(elem) { var tooltip = this; $(elem).each(function(){ var t = $(this), text = t.text(); if (HexColorTooltip.colorRegex.test(text)) { t.mousemove(function(){ tooltip.setColor(text).show(); }).mouseout(function(){ tooltip.hide(); }); } }); }, setColor: function(color) { return this.color === color ? this : this.css('background', this.color = color); }, show: function() { /// Overriding jQuery.fn.show this.css({ top: this.mouseY + this.offsetTop, left: this.mouseX + this.offsetLeft }); // Call super return this.fn.show.call(this); } }); // USAGE var tooltip = new HexColorTooltip(); tooltip.bindTo('span'); |
I don’t know if others will be interested in something like this. For many developers who use jQuery this might seem completely different to the jQuery you know and love, for others, maybe you’ll see it as a move in the right direction.
Please note that it’s experimental.
Please also note that this isn’t the same as jQuery.sub
:
A = jQuery.sub(); var a = new A; a.foo = 'Foo'; a[0] = document.createElement('div'); a.length = 1; // Chaining not reliable because some methods return a new jQ a.prependTo('body').foo // => UNDEFINED |
Also, jQuery.Interface provides a helpful setDOM
method. Fundamentally, jQuery.sub and jQuery.Interface are trying to solve slightly different problems, as far as I can see.
Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!
Hey, what about doing something like:
So Foo preserves
.constructor
So is the purpose of this plugin to do better interface element creation/modification?
I’ve been digging deep into Backbone.js these days. And its philosophy seems to be User Interaction -> DOM event listener -> Data modification callbacks -> More other data modification callbacks if needed -> interface updates signals -> populate javascript template like ERB -> update interface.
I think this is the ultimate separation. I know it’s know like the more separation the merrier. But I have something really against doing anything like append(‘…’).
All said, very interesting plugin, I guess this will be more middle ground between jQuery spaghetti and Backbone/knockout/javascriptmvc.
all right, just previewed my comment, I meant “it’s *NOT* like the more separation the merrier…” and I dislike injecting html elements so directly from javascript.
I like your jquery hacks. I’m not sure if I’ll use what you posted here, but I really enjoy your ideas to do something new and hacky with jquery. I’m a fan of that since jquery.single 🙂
“@Nik: All said, very interesting plugin, I guess this will be more middle ground between jQuery spaghetti and Backbone/knockout/javascriptmvc.”
That’s exactly how I see it too. I guess it can be helpful for little apps/widgets which do not need the whole Backbone logic and weight (not to mention _.js).
How about using jQueryUI.widget (http://wiki.jqueryui.com/w/page/12138135/Widget-factory)? Here is a quote from the that wiki page:
“It is designed not only for plugins that are part of jQuery UI, but for general consumption by developers who want to create object-oriented components without reinventing common infrastructure.”
This is exactly what you wanted to achieve here. I have used it before, and it makes it really easy to create a stateful ui components. So the tool tip example could have been easily made using the widget factory class.
Here
function Planet() {
//.+ Implement planet logic
}
Planet.prototype = jQuery([]);