I'm contemplating two new jQuery plugins, but wanted some feedback, both on the implementation, as well as if it's worth doing.
Here are the two pieces of functionality/problems I'm hoping to address (and they're somewhat related):
YUI has a feature in Nodes and NodeLists with .get
. It supports both:
- If the property you're accessing is a DOM node or Window or Document object, it will automatically wrap it with a Node/NodeList object. Here's an example:
A.one('#foo').get('parentNode'); // returns a wrapped YUI Node object pointing to the parent
- It also supports deeply nested property calls safely. Example:
A.one('#foo').get('parentNode.parentNode.parentNode');
// or
A.one('#someIframe').get('contentWindow.document.body');
The benefits of this are that you don't have to do multiple assignment/validations like this: Current behavior for the iframe example above:
var contentWindow = $('#someIframe').prop('contentWindow');
if(contentWindow){
var doc = contentWindow.document;
if(doc) {
var bd = $(doc.body);
}
}
My proposal is creating a plugin that adds a $prop
method. This method would be used like so:
$('body').$prop('parentNode'); // returns jQuery(<html>);
$('#someIframe').$prop('contentWindow.document.body'); // returns jQuery(<body>);
$('#foo').$prop('parentNode.addEventListener'); // returns the addEventListener method
As the last line shows, it would return the property unwrapped if it's not a DOM element.
YUI would automatically wrap event properties that referred to DOM elements with Node objects.
Example:
A.one('body').on('click', function(event){
// These all work:
event.currentTarget.all('span');
event.target.all('span');
event.relatedTarget.all('span');
});
In jQuery, they have an extra property called delegateTarget
(for use when delegating events), but the equivalent of above is:
$('body').on('click', function(event){
$(event.currentTarget).find('span');
$(event.target).find('span');
$(event.relatedTarget).find('span');
});
My proposal is to create a plugin that extends the Event object's prototype to add a $
method that will wrap whatever property you specify.
Example:
$('body').on('click', function(event){
// These all would work
event.$('currentTarget').find('span');
event.$('target').find('span');
event.$('relatedTarget').find('span');
event.$('delegateTarget').find('span');
});
There would also be a shortcut for accessing the currentTarget:
$('body').on('click', function(event){
// These all return the same thing:
event.$();
event.$('currentTarget');
event.$('this');
});
(It would basically just wrap any property name you gave it with jQuery so you could combine it with the above $prop
method like so:
$('body').on('click', {some: {deep: {value: 33}}}, handleClick);
function handleClick(event){
event.$('data').$prop('some.deep.value'); // returns 33
}
though this wouldn't be the driving case, and not even really required... I just thought that it would be better if you could guarantee a jQuery object no matter what it accesses.)
I've been bouncing back and forth on the proper approach for handling the automatic event property wrapping that YUI has.
My first attempt was to monkey patch the $.event.fix
function, and add in our wrapped objects.
Now, we wouldn't want to just straight up replace the current properties, because anyone using jQuery in Liferay would then get some behavior they're not expecting, as event.currentTarget
would be a jQuery object, which would break existing plugins as well.
So the alternative there would be to create new matching properties like so: event.$currentTarget
, event.$target
, etc.
There are some downsides to this though:
- Monkey patching the
$.event.fix
is just super hokey. There are all sorts of ways it could go wrong. - It would also mean we would need to keep up with any custom properties jQuery adds across upgrades. Not fun.
- It would also mean a performance hit for ALL events by doing 4 $() operations per event execution, even if the event handler doesn't need those objects wrapped.
The upsides to extending the $.Event.prototype
is:
- The
Event
object is exposed for this specific reason - The
$()
operations are on demand, so there's no performance impact - It's future proof to any new properties that are added (or any that are added by plugins)
Thoughts?
And about soaked property accessors… awesome! Didn’t know of it! But… did you grep’d Alloy UI codebase for occurences?
\.get\s*\(('|")[^'"]+\.
will do the job