views:

255

answers:

6

I was wondering if there was any unobtrusive way to hook into methods such as attr, data, css, etc and call custom triggers?

Ideally, I could do something like this:

$(".friend a").bind("attr.changed", changed /* data */, function(e) {
    alert("The " + changed.attribute + " attribute changed from " + changed.from + " to " + changed.to + "!");
});

$(".friend").append(
  $("<a/>").
    attr("href", "#").
    html("Friend 1").
    click(function() { alert('I was clicked!'); }); // creates the element, doesn't execute since element didn't exist

$(".friends a").each(function() {
  var $this = $(this);
  $this.attr("rel", "friend"); // triggers "attr.changed"
});

Ideally this would be able to be triggered on any element and pass the attr changed, from and to in an object to the trigger call from inside each jQuery method.

A: 

There is no event for this currently existing within jQuery.

The livequery plugin may provide something of what you need, such as changing classes. Basically the selector is saved and then any changes in the DOM that change what the selector affects then calls the passed functions, etc.

The Wicked Flea
A: 

And why don't you use the simplest approach:

jQuery.fn.changeAttr = function(attrName,attrValue) {
  return this.each(function(){
    this.attr(attrName,attrValue);
    alert('The att: '+attrName+ ' is now '+attrValue);
  });
};

And then just:

$('.friends a').changeAttr('rel','friend')

Sorry if it doesn't work, I don't remember exactly the syntax for extending jQuery.

Ricardo Vega
A: 

Here's a modification to attr() along the lines of what you're talking about.

(function(attr) {

    jQuery.attr = function(elem, name, value) {
        var current = attr(elem, name, undefined); // read current value
        var retval = current;

        if (value !== undefined) { // writing
            retval = attr(elem, name, value); // call original
            jQuery.event.trigger('attr.changed', {
                attribute: name,
                from: current,
                to: value
            }, elem)
        }

        return retval; // return original
    }

})(jQuery.attr);

Usage should be:

$(".friend a").bind("attr.changed", function(e, changed) {
    alert("The " + changed.attribute + " attribute changed from " + changed.from + " to " + changed.to + "!");
});

$(".friends a").attr("class", "foo"); // triggers alert

I don't believe css() is possible as the .style property can't trigger events. data() I don't know.

Note I don't condone its usage, this was just an academic effort. What you want incurs a performance penalty on attr() in order to populate the from property of the event argument. We have to read the old value before we write the new one.

Crescent Fresh
+4  A: 

It's pretty easy to write a "hook" function:

function hook(original, wrapper) {
    return function() {
        wrapper.apply(this, arguments);
        return original.apply(this, arguments);
    }
}

The wrapper function you provide will be called with the same arguments as the original function, before the original function itself is called.

An example of use:

$.fn.attr = hook($.fn.attr, function(attribute, value) {
    alert('attribute: '+attribute+', value: '+value);
});
$('a.pie').attr('href', 'http://blueberry.pie');
// An alert says "attribute: href, value: http://blueberry.pie"

(Sidenote: It'd be an easy extension to let your wrapper cancel the call to the original function, as well, but that's getting more featureful than you wanted.)

You could either use this directly to do what you want, or you could make the wrapper function just fire custom jquery events which you listen for in the standard jquery ways.

David
+1  A: 

Turns out this hack will work; I use data's built-in setData triggers (and the aforementioned 'hook' to add functionality atop jQuery's attr and removeAttr) to duplicate attrs within the data object on the jQuery object. Although it's somewhat dirty, I get the functionality to hook into data-change triggers for data, attr, and, if written, any other jQuery methods that track key/value pairs.

(function($) {
  var binder = function(e, dataKey, dataValue) {
    return (function(dataKey, dataValue, self) {
      var $this = $(self),
          oldValue = $this.data(dataKey),
          newValue = dataValue,
          passed = {
            attr: dataKey,
            from: oldValue,
            to:   newValue
          };

      var isAttribute = !!($this.data(dataKey + "-attribute"));

      if(oldValue !== newValue) { 
        var attrPrefix = isAttribute ? "attr-" : "";
        $this.trigger(attrPrefix + dataKey + "-changed", passed); 
        $this.trigger(attrPrefix + "data-changed", passed); 
      }
    })(dataKey, dataValue, this);
  };

  var hook = function(original, wrapper) {
    return function() {
      wrapper.apply(this, arguments);
      return original.apply(this, arguments);
    };
  };

  $.fn.attr = hook($.fn.attr, function(attr, val) {
    if(val) {
      $(this).data(attr + "-attribute", true);
      $(this).data(attr, val);
    }
  });

  $.fn.removeAttr = hook($.fn.removeAttr, function(attr) {
    $(this).removeData(attr + "-attribute");
    $(this).removeData(attr);
  });

  $.fn.observeData = function() {
    $(this).bind("setData", binder);
  };
})(jQuery);
A: 

function hook(original, wrapper) {
return function() { wrapper.apply(this, arguments);
return original.apply(this, arguments); } }

This is not a "hook" this is sequential execution of wrapper and then original ... "wrapper" can be only "wrapped arround" as its name suggests. Even then above function is just confused juvenile programming. obviously mistakenly copied from some "awesome" blog ...

Also, here 'argumets' have no role whatsoever. Also, why returning a function ? Why using apply? this of the hook is completely irrelevant above, so why passing it into both wrapper and original ?