Skip to content

Instantly share code, notes, and snippets.

@natecavanaugh
Last active August 29, 2015 14:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save natecavanaugh/a0b56312c6a872fe3724 to your computer and use it in GitHub Desktop.
Save natecavanaugh/a0b56312c6a872fe3724 to your computer and use it in GitHub Desktop.

Possible jQuery plugins

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):

Problem/Feature 1

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);
    }
}

Proposal for #1

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.

Problem/Feature 2

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');
});

Proposal for #2

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.)

Reasoning

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:

  1. Monkey patching the $.event.fix is just super hokey. There are all sorts of ways it could go wrong.
  2. It would also mean we would need to keep up with any custom properties jQuery adds across upgrades. Not fun.
  3. 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:

  1. The Event object is exposed for this specific reason
  2. The $() operations are on demand, so there's no performance impact
  3. It's future proof to any new properties that are added (or any that are added by plugins)

Thoughts?

@yuchi
Copy link

yuchi commented Oct 30, 2014

My 2 cents:

  1. I personally hate $ prefixes
  2. Monkey-patching a method is not necessary for performance reasons, Liferay is going to drop support for IE8 so you’ll be able to use Object.defineProperty in its full form
  3. Do you really need it? Remember you create not only a framework, but a standard that LR developers will follow (if only by copy-paste). That means that code for Lifer—I mean—for Alloy will not work outside your walled garden.

An example of how you could patch the Event object in ES5:

Object.defineProperty($.Event.prototype, '$currentTarget', {
  get: function () {
    var result = $(this.currentTarget);
    Object.defineProperty(this, '$currentTarget', { value: result });
    return result;
  }
});

@yuchi
Copy link

yuchi commented Oct 30, 2014

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment