4

Here jsFiddle to test sample

I'm currently writing a jQuery snippet to handle any html content change in DOM 'triggered' by any jQuery domManip function (extending some functions). Not sure it's the best way to do it, so any advice will be welcome.

This snippet works as expected if bound to document. However, if I try to bind it to a specific element, I'm facing problem which some function as .remove(). Maybe it's due to custom event not using normal propagation behaviour but I'm really not sure.

This is a working sample, I bind contentChange event to document, works cross-browser as I can test it: {Firefox, IE9, Chrome and Safari under Win7}

;
(function ($) {
    $.fn.contentChange = function (types, data, fn) {
        return this.on('contentChange', types, null, data, fn);
    };
    var oDomManip = $.fn.domManip,
        oHtml = $.fn.html,
        oEmpty = $.fn.empty,
        oRemove = $.fn.remove,
        extendFct = function (oFct, sender, args) {
            return oFct.apply(sender, args), $.event.trigger('contentChange');
            //=>if testing specific element (#test) use instead following line
             //return oFct.apply(sender, args), $(sender).trigger('contentChange');
        };
    $.fn.domManip = function () {
        extendFct(oDomManip, this, arguments)
    };
    $.fn.html = function () {
        extendFct(oHtml, this, arguments)
    };
    $.fn.empty = function () {
        extendFct(oEmpty, this, arguments)
    };
    $.fn.remove = function () {
        extendFct(oRemove, this, arguments)
    };
   
})(jQuery);

I use: $.event.trigger('contentChange') to trigger custom event.

Called like it:

$(document).contentChange(function () {
    console.log("onContentChange")
});

However, if I use:

$('#test').contentChange(function () {
    console.log("onContentChange")
});

The custom event is not triggered. So, to trigger a custom event on a specific element, I can triggered it like this:

$(sender).trigger('contentChange');

But now, call to remove() method on self or children doesn't triggered my custom event. I can understand that event callback function won't be called if I remove the element, but why isn't it called when removing children (while it's working if bound to document!)?

I was expecting this line to make custom event bubbles to '#test':

$('#test').find('div:first').remove();

Is there any way to triggered this custom event bound to a specific element when manipulating this element and/or its children?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
A. Wolff
  • 74,033
  • 9
  • 94
  • 155
  • shouldn't `$.event.trigger('contentChange')` be instead triggering an event on an element? such as the one that was manipulated? side-note, this is the technique that was used by livequery to get a similar effect, i'd suggest looking into how livequery did it for possible improvements. – Kevin B Apr 10 '13 at 14:13
  • Why you doesn't use [DOMSubtreeModified](http://stackoverflow.com/questions/2844565/is-there-a-jquery-dom-change-listener) – alexP Apr 10 '13 at 14:21
  • @alexP http://stackoverflow.com/questions/6659662/why-is-the-domsubtreemodified-event-deprecated-in-dom-level-3 – Kevin B Apr 10 '13 at 14:24

2 Answers2

1

You need to trigger the event on the element that was modified.

http://jsfiddle.net/Gw4Lj/2/

return oFct.apply(sender, args), sender.trigger('contentChange');

however, with that change, you will no longer catch the event that was triggered on an element that isn't connected to the DOM because it isn't a descendant of that document, which is ok in my opinion because it isn't associated to that DOM, it's in a DOM Fragment.

Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • Like you said i cannot catch event on removed element. This is what i'm looking for. Using $.event.trigger('contentChange') with $(document).contentChange() triggered event even element is removed from DOM. As i understand it, this is because of using $.event but then i cannot specify an element. I was looking for $('#test').contentChange() beeing triggered even element or children been removed. Even like this: return sender.trigger('contentChange'),oFct.apply(sender, args); it is not working for children (but works for self element). – A. Wolff Apr 10 '13 at 14:31
  • @roasted but... why should the document be notified of a change that doesn't affect the document? However, you could detect whether or not the element is part of the document, and if it isn't trigger the event both on the element AND the document. – Kevin B Apr 10 '13 at 14:34
  • Basically, i want to triggered event of a specific div whenever a element is added/removed. This seems to work if using $(document) with $.event.trigger('contentChange'), but not using $('#test') with $.event.trigger('contentChange'). I dont know why? – A. Wolff Apr 10 '13 at 14:45
  • It doesn't trigger on #test because $.event.trigger('contentChange') doesn't have anything to do with #test. – Kevin B Apr 10 '13 at 14:47
  • It makes no sense to trigger the event on the element AND on the document, that will result in either the document receiving the event twice, or the plugin becoming far less useful due to having to stop the event from propagating to prevent the document receiving the event twice. – Kevin B Apr 10 '13 at 14:57
1

I come with slightly modified version wich seems to work fine for the purpose i reach. Need optimization for the .on() method extend, so please feel free to share your feedbacks.

Inspired from here: https://groups.google.com/forum/?fromgroups=#!topic/jquery-dev/ZaMw2XB6wyM

Thanks to Wil Stuckey

Here jsFiddle

;(function ($) {
    var fctsToObserve = {
        append: [$.fn.append, 'self'],
        prepend: [$.fn.prepend, 'self'],
        remove: [$.fn.remove, 'parent'],
        before: [$.fn.before, 'parent'],
        after: [$.fn.after, 'parent']
    }, fctsObserveKeys = '';
    $.each(fctsToObserve, function (key, element) {
        fctsObserveKeys += "hasChanged." + key + " ";
    });
    var oOn = $.fn.on;
    $.fn.on = function () {
        if (arguments[0].indexOf('hasChanged') != -1) arguments[0] += " " + fctsObserveKeys;
        return oOn.apply(this, arguments);
    };
    $.fn.hasChanged = function (types, data, fn) {
        return this.on(fctsObserveKeys, types, null, data, fn);
    };
    $.extend($, {
        observeMethods: function (namespace) {
            var namespace = namespace ? "." + namespace : "";
            var _len = $.fn.length;
            delete $.fn.length;
            $.each(fctsToObserve, function (key) {
                var _pre = this;
                $.fn[key] = function () { 
                    var target = _pre[1] === 'self' ? this : this.parent(),
                        ret = _pre[0].apply(this, arguments);
                    target.trigger("hasChanged." + key + namespace, arguments);
                    return ret;
                };
            });
            $.fn.length = _len;
        }
    });
    $.observeMethods()
})(jQuery);
A. Wolff
  • 74,033
  • 9
  • 94
  • 155