Skip to content

Instantly share code, notes, and snippets.

@Skateside
Created March 27, 2014 11:32
Show Gist options
  • Save Skateside/9805540 to your computer and use it in GitHub Desktop.
Save Skateside/9805540 to your computer and use it in GitHub Desktop.
Nothing short of a hack, but a surprisingly useful one when you can't change the main scripts.
/**
* Add a handler to check if a class changes on a given element.
* The handler will get two arguments: the new class and the old class.
*
* @param {Element} element Element whos class changes should be watched.
* @param {Function} func Function to call when the class changes.
*/
var onclasschange = (function () {
'use strict';
var MutationObserver = null,
classWatcher = null,
pollTime = 125;
/**
* Checks to see if the DOMAttrModified MutationEvent works correctly (it
* does not in Webkit)
*
* http://engineering.silk.co/post/31921750832/mutation-events-what-happens?
* https://bugs.webkit.org/show_bug.cgi?id=8191
*
* @return {boolean} true if the DOMAttrModified MutationEvent is working
* correctly, false otherwise.
*/
function isMutationEventsWorking() {
var attrModifiedWorks = false,
listener = function () {
attrModifiedWorks = true;
},
documentElement = document.documentElement;
documentElement.addEventListener("DOMAttrModified", listener, false);
documentElement.setAttribute("___TEST___", true);
documentElement.removeAttribute("___TEST___", true);
documentElement.removeEventListener("DOMAttrModified", listener, false);
return attrModifiedWorks;
}
/**
* A simple check to see if MutationObservers exist (even if it's in a
* prefixed state).
*
* @type {?Function} A MutationObserver function if found or null if the
* browser does not support MutationObservers.
*/
MutationObserver = window.MutationObserver || (function () {
var prefixes = ['Ms', 'O', 'Moz', 'Webkit'],
i = 0,
il = prefixes.length,
Obs = null,
temp = null;
while (i < il) {
temp = window[prefixes[i] + 'MutationObserver'];
if (typeof temp === 'function') {
Obs = temp;
break;
}
i += 1;
}
return Obs;
}());
/**
* If the browser supports MutationObservers, we can use the most efficient
* way of tracking a class change.
*/
if (MutationObserver) {
classWatcher = function (element, func) {
var observer = new MutationObserver(function (mutations) {
var i = 0,
il = mutations.length,
m = null;
while (i < il) {
m = mutations[i];
if (m.type === 'attributes' &&
m.attributeName === 'class') {
/**
* In some browsers, oldValue can be null before the
* element has a className. We swap that to an empty
* string to make the function parameters easier to
* manage (and it's still accurate).
*/
func.call(element, element.className, m.oldValue || '');
break;
}
i += 1;
}
});
observer.observe(element, {
attributes: true,
childList: true,
characterData: true,
attributeOldValue: true
});
};
/**
* If the browser does not support the MutationObserver but does support the
* MutationEvent, we should use that. We should be careful to check that
* DOMAttrModified works correctly since that is the event we will bind to.
*/
} else if (('MutationEvent' in window) &&
typeof document.addEventListener === 'function' &&
isMutationEventsWorking()) {
classWatcher = function (element, func) {
element.addEventListener('DOMAttrModified', function (e) {
if (e.attrName === 'class') {
func.call(element, e.newValue, e.prevValue);
}
}, false);
};
/**
* If MutationObserver and MutationEvent support doesn't exist in the
* browser, we make a quick check for MSIE's onpropertychange event. Binding
* to that event will be more efficient than the polling.
*/
} else if ('onpropertychange' in document.createElement('_') &&
typeof document.attachEvent !== 'undefined') {
classWatcher = function (element, func) {
var oldValue = element.className;
element.attachEvent('onpropertychange', function () {
if (window.event.propertyName === 'className') {
func.call(element, element.className, oldValue);
oldValue = element.className;
}
});
};
/**
* At this stage, MutationObserver, MutationEvent and onpropertychange are
* not supported by the browser. Our only alternative is to repeatedly check
* an element to see if a value has changed and execute the function if it
* has.
*/
} else {
classWatcher = function (element, func) {
var oldValue = element.className,
poll = function () {
var className = element.className;
if (className !== oldValue) {
func.call(element, className, oldValue);
oldValue = className;
}
window.setTimeout(poll, pollTime);
};
poll();
};
}
return classWatcher;
}());
@Skateside
Copy link
Author

Just thinking aloud here ... could pass the newClass and oldClass arguments through something like this, to make it easier to check if a certain class was contained:

function ClassName() {
    return this.init.apply(this, arguments);
}

ClassName.prototype = {

    init: function (className) {

        this.className = className;

    },

    toString: function () {

        return this.className;

    },

    escape: function (source) {

        return source.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');

    },

    contains: function (str) {

        var reg = new RegExp('\\b' + this.escape(str) + '\\b');

        return reg.test(this.className);

    }

};

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