Skip to content

Instantly share code, notes, and snippets.

@Shine-neko
Created May 19, 2015 08:30
Show Gist options
  • Save Shine-neko/e5a75c97d633822a531c to your computer and use it in GitHub Desktop.
Save Shine-neko/e5a75c97d633822a531c to your computer and use it in GitHub Desktop.
(function ($, undefined) {
$.fn.watch = function (options) {
/// <summary>
/// Allows you to monitor changes in a specific
/// CSS property of an element by polling the value.
/// when the value changes a function is called.
/// The function called is called in the context
/// of the selected element (ie. this)
///
/// Uses the MutationObserver API of the DOM and
/// falls back to setInterval to poll for changes
/// for non-compliant browsers (pre IE 11)
/// </summary>
/// <param name="options" type="Object">
/// Option to set - see comments in code below.
/// </param>
/// <returns type="jQuery" />
var opt = $.extend({
// CSS styles or Attributes to monitor as comma delimited list
// For attributes use a attr_ prefix
// Example: "top,left,opacity,attr_class"
properties: null,
// interval for 'manual polling' (IE 10 and older)
interval: 100,
// a unique id for this watcher instance
id: "_watcher",
// flag to determine whether child elements are watched
watchChildren: false,
// Callback function if not passed in callback parameter
callback: null
}, options);
return this.each(function () {
var el = this;
var el$ = $(this);
var fnc = function (mRec, mObs) {
__watcher.call(el, opt.id, mRec, mObs);
};
var data = {
id: opt.id,
props: opt.properties.split(','),
vals: [opt.properties.split(',').length],
func: opt.callback, // user function
fnc: fnc, // __watcher internal
origProps: opt.properties,
interval: opt.interval,
intervalId: null
};
// store initial props and values
$.each(data.props, function(i) {
if (data.props[i].startsWith('attr_'))
data.vals[i] = el$.attr(data.props[i].replace('attr_',''));
else
data.vals[i] = el$.css(data.props[i]);
});
el$.data(opt.id, data);
hookChange(el$, opt.id, data);
});
function hookChange(element$, id, data) {
element$.each(function () {
var el$ = $(this);
if (window.MutationObserver) {
var observer = el$.data('__watcherObserver');
if (observer == null) {
observer = new MutationObserver(data.fnc);
el$.data('__watcherObserver', observer);
}
observer.observe(this, {
attributes: true,
subtree: opt.watchChildren,
childList: opt.watchChildren,
characterData: true
});
} else
data.intervalId = setInterval(data.fnc, interval);
});
}
function __watcher(id,mRec,mObs) {
var el$ = $(this);
var w = el$.data(id);
if (!w) return;
var el = this;
if (!w.func)
return;
var changed = false;
var i = 0;
for (i; i < w.props.length; i++) {
var key = w.props[i];
var newVal = "";
if (key.startsWith('attr_'))
newVal = el$.attr(key.replace('attr_', ''));
else
newVal = el$.css(key);
if (newVal == undefined)
continue;
if (w.vals[i] != newVal) {
w.vals[i] = newVal;
changed = true;
break;
}
}
if (changed) {
// unbind to avoid recursive events
el$.unwatch(id);
// call the user handler
w.func.call(el, w, i, mRec, mObs);
// rebind the events
hookChange(el$, id, w);
}
}
}
$.fn.unwatch = function (id) {
this.each(function () {
var el = $(this);
var data = el.data(id);
try {
if (window.MutationObserver) {
var observer = el.data("__watcherObserver");
if (observer) {
observer.disconnect();
el.removeData("__watcherObserver");
}
} else
clearInterval(data.intervalId);
}
// ignore if element was already unbound
catch (e) {
}
});
return this;
}
String.prototype.startsWith = function (sub) {
if (sub === null || sub === undefined) return false;
return sub == this.substr(0, sub.length);
}
})(jQuery, undefined);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment