├── .gitignore ├── LICENSE ├── README.md ├── example └── index.html ├── package.json └── src ├── jquery.onmutate.js └── jquery.onmutate.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .jshintrc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 eclecto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery-onMutate 2 | =============== 3 | 4 | This plugin is a wrapper for the MutationObserver class, with a fallback to setInterval, so you can execute code when elements matching a given selector are created or have their attributes or text changed. It includes three methods: .onCreate(), .onModify(), .onText(), and .onMutate(). 5 | 6 | This plugin is dependent on jQuery (tested on version 1.6.4) 7 | 8 | Installation 9 | ------------ 10 | 11 | ``` 12 | npm install --save jquery-onmutate 13 | ``` 14 | 15 | Usage 16 | ----- 17 | 18 | ``` 19 | var onMutate = require('jquery-onmutate'); 20 | var jQuery = require('jquery'); 21 | 22 | onMutate(jQuery); 23 | ``` 24 | 25 | API 26 | === 27 | 28 | .onCreate() 29 | ----------- 30 | 31 | Attaches a listener to an existing parent element, and executes a callback when a new descendent element matching a given selector is created. If a matching element already exists, the callback will execute immediately. 32 | 33 | `parent.onCreate(selector, callback[, multi = false]);` 34 | 35 | **parent** (jQuery) a single parent element that is a known ancestor of the created element(s). Only the first element in the set will be used. 36 | 37 | **selector** (string) is a valid jQuery selector for the new element(s). OnMutate will poll for a valid new element using `jQuery(selector, parent)` 38 | 39 | **callback** (function) the callback executed when a new matching element is created. It receives one argument, **elements**, which is a jQuery object containing the new elements. 40 | 41 | **multi** (boolean) determines whether the observer will continue to poll for new elements after a match is found. If false, the observer will shut itself off after the first match(es); this includes if matching elements already exist when onCreate is called. Otherwise, it will continue to poll, but will only operate on each created element once. 42 | 43 | `$.onCreate()` will attach the onCreate listener to the document, i.e. is the same as `$(document).onCreate()`. 44 | 45 | `parent.onCreate('detach');` 46 | 47 | This usage shuts off the onCreate listener. 48 | 49 | .onModify() 50 | ----------- 51 | 52 | Attaches a listener to a single element, and executes a callback whenever one of its attributes is changed with optional conditions. While the callback executes the listener will be momentarily shut off, to prevent infinite loops if the callback also changes the element's attributes. 53 | 54 | `element.onModify([attributes[, match,]] callback[, multi = false]);` 55 | 56 | **element** (jQuery) the element to be observed. 57 | 58 | **attributes** (string) is a space-separated list of attributes to watch. If omitted, the callback will execute on any attribute change. 59 | 60 | **match** (string|RegExp) is a string or RegExp that will be matched against the new value of any modified attribute(s). If you set a match then you must also specify the **attributes** to watch. 61 | 62 | **callback** (function) is the function to execute when a qualifying change is made to the element's attributes. It receives one argument, **element**, which is a jQuery object containing the affected element. 63 | 64 | **multi** (boolean) determines whether the observer will continue to poll for changes after the callback executes. If false, the observer will shut itself off after the first qualifying change; otherwise, it will continue to poll. 65 | 66 | `element.onModify('detach');` 67 | 68 | This usage shuts off the onModify listener. 69 | 70 | .onText() 71 | --------- 72 | 73 | Attaches a listener to a single element, and executes a callback whenever its text content changes with optional conditions. While the callback executes the listener will be momentarily shut off, to prevent infinite loops if the callback also changes the element's text. 74 | 75 | `element.onText([match,] callback[, multi = false])` 76 | 77 | **element** (jQuery) is the element to be observed. 78 | 79 | **match** (string|RegExp) is an optional string or RegExp that will be matched against element's new text content, as read with jQuery's `.text()` method. 80 | 81 | **callback** (function) is the function to execute when a qualifying change is made to the element's text content. It receives one argument, `element`, which is a jQuery object containing the affected element. 82 | 83 | **multi** (boolean) determines whether the observer will continue to poll changes after the callback executes. If false, the observer will shut itself off after the first qualifying change; otherwise, it will continue to poll. 84 | 85 | `element.onText('detach');` 86 | 87 | This usage shuts off the onText listener. 88 | 89 | .onMutate() 90 | ----------- 91 | 92 | An alternative way to call one of the methods above, passing the type of mutation as an argument. 93 | 94 | `element.onMutate(type, ...)` 95 | 96 | **element** (jQuery) is the element to be observed. 97 | 98 | **type** (string) should be "create", "modify", or "text". 99 | 100 | Additional arguments are specific to the method being invoked. 101 | 102 | Additional Notes: 103 | ----------------- 104 | - .onCreate() internally creates a MutationObserver that observes with the `childList` and `subtree` options. 105 | - .onModify() internally creates a MutationObserver that observes with the `attributes` option. 106 | - .onText() internally creates a MutationObserver that observes with the `characterData`, `childList`, and `subtree` options. This is so it can recognize text node insertion and deletion as a text change, as well as observing characterData changes to existing text nodes. 107 | - For browsers that do not support MutationObserver, this plugin will check all observed elements at 50ms intervals for changes that match the conditions. Because of this, there can be a slight delay/flash before the callback executes for these browsers. -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | onMutate Demo 6 | 7 | 8 | 9 | 10 | 11 | 147 | 148 | 149 | 150 | 380 | 381 | 382 | 383 | 384 | 385 |
386 |

jQuery onMutate plugin

387 |
388 |
389 |

onCreate

390 |
0 boxes
391 |

Click the button to create a blue box. onCreate listeners will act on it depending on the behavior you choose.

392 | 393 |
394 | 395 | 396 | 397 |
398 |
399 | 401 | 403 | 405 | 407 | 408 |
409 |
410 |
411 |
412 |
413 |

onModify

414 |
415 |

Click on the box to turn it green. onModify listeners will act on it according to the behavior you choose.

416 | 417 |
418 | 419 |
420 |
421 | 423 | 425 | 427 |
428 |
429 |
This is a blue box
430 |
431 |
432 |
433 |

Drag to resize this box. It will turn orange at its minimum size and green at its maximum size.

434 |
This box has 17,900 pixels.
435 |
436 |
437 |
438 |

onText

439 |

Click on the box to change its text, making it claim it's green. onText listeners will act on it according to the behavior you choose.

440 | 441 |
442 | 443 |
444 |
445 | 447 | 449 | 451 |
452 |
453 |
This is a blue box
454 |
455 |
456 |
457 |
458 | 459 | 460 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-onmutate", 3 | "version": "1.4.2", 4 | "description": "A MutationObserver wrapper for jQuery, allowing you to listen for element creation, attribute modification, and text changes.", 5 | "main": "src/jquery.onmutate.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "bump": "npm version patch -m \"bump to v%s\" && npm publish && git push --follow-tags" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/eclecto/jQuery-onMutate.git" 16 | }, 17 | "keywords": [ 18 | "mutationobserver", 19 | "jquery-plugin", 20 | "ecosystem:jquery" 21 | ], 22 | "author": "Eric Newland", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/eclecto/jQuery-onMutate/issues" 26 | }, 27 | "homepage": "https://github.com/eclecto/jQuery-onMutate" 28 | } 29 | -------------------------------------------------------------------------------- /src/jquery.onmutate.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery onMutate plugin v1.4 3 | * http://jquery.com/ 4 | * 5 | * Copyright 2016 CROmetrics 6 | * Released under the MIT license 7 | * https://github.com/eclecto/jQuery-onCreate/blob/master/LICENSE 8 | * 9 | * Date: 2016-08-23T13:12Z 10 | */ 11 | 12 | /* global define, module, setTimeout */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | define(['jquery'], factory); 18 | } else if (typeof exports !== 'undefined') { 19 | module.exports = factory; 20 | } else { 21 | factory(jQuery); 22 | } 23 | 24 | }(function ($, MutationObserver) { 25 | if (!$) $ = ($.fn.jquery ? $ : jQuery); 26 | if (!MutationObserver) MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || false; 27 | var cbid = 0; 28 | 29 | // Uncomment this to easily test the setInterval fallback. 30 | // MutationObserver = false; 31 | 32 | // Constants for mutation types. 33 | var CREATE = 'create', 34 | MODIFY = 'modify', 35 | TEXT = 'text'; 36 | 37 | // Observer class. 38 | function Observer(mutcallback) { 39 | var realObserver; 40 | 41 | // MutationObserver.observe() polyfill 42 | this.observe = function (element, init) { 43 | if (MutationObserver) { 44 | realObserver = realObserver || new MutationObserver(mutcallback); 45 | realObserver.observe(element, init); 46 | } else { 47 | realObserver = setInterval(mutcallback, 50); 48 | } 49 | }; 50 | 51 | // MutationObserver.disconnect() polyfill 52 | this.disconnect = function () { 53 | if (realObserver) { 54 | if (MutationObserver) { 55 | realObserver.disconnect(); 56 | } else { 57 | clearInterval(realObserver); 58 | } 59 | } 60 | }; 61 | } 62 | 63 | // Callback class to wrap a callback and its properties 64 | function Callback(callback, conditions, multi) { 65 | this.callback = callback; 66 | this.conditions = conditions; 67 | this.multi = multi || false; 68 | this.cbid = cbid++; 69 | this.processed = $([]); 70 | } 71 | 72 | // Create a map of an element's attributes so we can poll for changes (non-MutationObserver browsers) 73 | function attributeMap(element) { 74 | var map = {}; 75 | 76 | if (element.attributes) { 77 | $.each(element.attributes, function (index, attr) { 78 | map[attr.name] = attr.value; 79 | }); 80 | } 81 | 82 | return map; 83 | } 84 | 85 | // Returns a set of MutationRecord-like objects by comparing two attribute maps. 86 | function mapCompare(map1, map2) { 87 | var mutations = []; 88 | $.each(map1, function (key, value) { 89 | if (typeof (map2[key]) === 'undefined' || value !== map2[key]) { 90 | mutations.push({ 91 | attributeName: key 92 | }); 93 | } 94 | }); 95 | $.each(map2, function (key) { 96 | if (typeof (map1[key]) === 'undefined') { 97 | mutations.push({ 98 | attributeName: key 99 | }); 100 | } 101 | }); 102 | return mutations; 103 | } 104 | 105 | // Check attributes 106 | function checkAttrs(mutation, $el, callback) { 107 | var attrname = mutation.attributeName, 108 | attrval = $el.attr(attrname), 109 | conditions = callback.conditions || false, 110 | attrs = conditions ? conditions[0] : false, 111 | match = conditions && conditions[1] ? conditions[1] : false; 112 | // If no attrs are set or the mutation affects one of the targeted attributes 113 | if (!attrs || attrs.indexOf(attrname) > -1) { 114 | if (match) { 115 | return (attrval.search(match) >= 0); 116 | } else { 117 | return true; 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | 124 | // Possible methods to invoke with $().on[Create|Modify|Text]() 125 | var methods = { 126 | // Attach observers. 127 | attach: function (options) { 128 | var type = options.type, 129 | callback = options.callback, 130 | multi = options.multi || false, 131 | conditions = options.conditions; 132 | 133 | // Get/initiate the element's onMutate data. 134 | if(!this.data('onMutate')) this.data('onMutate', { 135 | create: { 136 | callbacks: [], 137 | ignore: false 138 | }, 139 | modify: { 140 | callbacks: [], 141 | ignore: false 142 | }, 143 | text: { 144 | callbacks: [], 145 | ignore: false 146 | } 147 | }); 148 | var om = this.data('onMutate')[type], 149 | callbacks = om.callbacks, 150 | // Add the callback to the array of callbacks for the current type. 151 | newcb = new Callback(callback, conditions, multi); 152 | callbacks.unshift(newcb); 153 | 154 | // Store the element's current text or attributes if needed. 155 | if (!MutationObserver && type === MODIFY) { 156 | om.attributeMap = attributeMap(this[0]); 157 | } 158 | if (type === TEXT) om.text = this.text(); 159 | 160 | var $this = this, 161 | i, changematch; 162 | 163 | // Define our callback for when a mutation is detected. 164 | var mutcallback = om.mutcallback = om.mutcallback || function (mutations) { 165 | callbacks = om.callbacks; // Refresh the callback list. 166 | // Ignore any DOM changes that this callback makes to prevent infinite loops. 167 | if (!om.ignore) { 168 | om.ignore = true; 169 | } else { 170 | return; 171 | } 172 | 173 | var newmap, newtext; 174 | if (type === MODIFY) newmap = attributeMap($this[0]); 175 | if (type === TEXT) newtext = $this.text(); 176 | 177 | for (i = callbacks.length - 1; i >= 0; i--) { 178 | console.log(callbacks[i]); 179 | var selected = (type === CREATE ? $(callbacks[i].conditions, $this) : $this); 180 | var proc = callbacks[i].processed; 181 | var elements = selected.not(proc); 182 | 183 | // Validate changed attributes for modify observers. 184 | if (type === MODIFY) { 185 | changematch = false; 186 | if (!MutationObserver) { 187 | mutations = mapCompare(newmap, om.attributeMap); 188 | } 189 | for (var j = 0; j < mutations.length; j++) { 190 | changematch = changematch || checkAttrs(mutations[j], $this, callbacks[i]); 191 | } 192 | } 193 | 194 | // Compare the text contents of the element to its original text. 195 | if (type === TEXT) { 196 | changematch = false; 197 | var cond = callbacks[i].conditions; 198 | if (newtext !== om.text) { 199 | if (cond) { 200 | changematch = newtext.search(cond) > -1; 201 | } else { 202 | changematch = true; 203 | } 204 | } 205 | } 206 | 207 | if (elements.length > 0 && (type === CREATE || changematch)) { 208 | if (type === CREATE) proc = proc.add(elements); 209 | callbacks[i].processed = proc; 210 | callbacks[i].callback.call($this, elements); 211 | // Remove callback from the list if it only runs once. 212 | if (!callbacks[i].multi) { 213 | callbacks.splice(i, 1); 214 | } 215 | } 216 | // Update the current values, accounting for any changes the callback(s) might have made. 217 | if (type === MODIFY) om.attributeMap = attributeMap($this[0]); 218 | if (type === TEXT) om.text = $this.text(); 219 | } 220 | 221 | // We've safely iterated through the callbacks, so don't ignore this master Callback anymore. 222 | // Additional mutation events apparently fire after this entire function, so we set ignore to false with an extremely small delay. 223 | if (om.ignore) setTimeout(function () { 224 | om.ignore = false; 225 | }, 1); 226 | if (callbacks.length === 0) { 227 | if (observer) { 228 | observer.disconnect(); 229 | } else { 230 | return true; 231 | } 232 | } 233 | }; 234 | // Sanity Check: If this is a create listener, run the callback on initialization to see if there is already a valid element. 235 | if (type === CREATE && mutcallback()) { 236 | return this; 237 | } 238 | 239 | var observer = om.observer = om.observer || new Observer(mutcallback), 240 | init; 241 | switch (type) { 242 | case CREATE: 243 | init = { 244 | childList: true, 245 | subtree: true 246 | }; 247 | break; 248 | case MODIFY: 249 | init = { 250 | attributes: true 251 | }; 252 | if (conditions) { 253 | init.attributeFilter = conditions[0].split(' '); 254 | } 255 | break; 256 | case TEXT: 257 | init = { 258 | childList: true, 259 | characterData: true, 260 | subtree: true 261 | }; 262 | break; 263 | } 264 | observer.observe($this[0], init); 265 | return this; 266 | }, 267 | 268 | // Detach observers. 269 | detach: function (options) { 270 | if (!this.data('onMutate')) return this; 271 | 272 | var type = options.type, 273 | om = this.data('onMutate')[type], 274 | callbacks = om.callbacks, 275 | callback = options.callback; 276 | if (callback) { 277 | for (var i = callbacks.length - 1; i >= 0; i--) { 278 | if (callbacks[i].callback.prototype.cbid === callback.prototype.cbid) { 279 | callbacks.splice(i, i + 1); 280 | } 281 | } 282 | } else { 283 | om.callbacks = []; 284 | } 285 | if (!callbacks.length) { 286 | om.observer.disconnect(); 287 | } 288 | return this; 289 | } 290 | }; 291 | 292 | $.fn.extend({ 293 | // $(parent).onCreate(selector, callback[, multi]); 294 | onCreate: function () { 295 | 296 | var args = Array.prototype.slice.call(arguments); 297 | 298 | // Invalid argument 299 | if (typeof (args[0]) !== 'string') { 300 | return this; 301 | } 302 | 303 | if (args[0] === 'detach') { 304 | return methods.detach.call(this, { 305 | type: CREATE, 306 | callback: args[1] 307 | }); 308 | } else 309 | // Default init functionality. 310 | if (typeof (args[0]) === 'string' && typeof (args[1]) === 'function') { 311 | return methods.attach.call(this, { 312 | type: CREATE, 313 | conditions: args[0], 314 | callback: args[1], 315 | multi: args[2] 316 | }); 317 | } else { 318 | return this; 319 | } 320 | }, 321 | 322 | // onModify. Usage: .onModify([attributes[, match,]] callback, multi) 323 | onModify: function () { 324 | 325 | var args = Array.prototype.slice.call(arguments), 326 | method; 327 | if (methods[args[0]]) { 328 | method = args.shift(); 329 | } else { 330 | method = 'attach'; 331 | } 332 | 333 | if (typeof (args[0]) === 'function') { 334 | return methods[method].call(this, { 335 | type: MODIFY, 336 | callback: args[0], 337 | multi: args[1] 338 | }); 339 | } else if (typeof (args[0]) === 'string') { 340 | if (typeof (args[1]) === 'function') { 341 | return methods[method].call(this, { 342 | type: MODIFY, 343 | conditions: [args[0]], 344 | callback: args[1], 345 | multi: args[2] 346 | }); 347 | } else if (typeof (args[1]) === 'string' || args[1] instanceof RegExp) { 348 | return methods[method].call(this, { 349 | type: MODIFY, 350 | conditions: [args[0], args[1]], 351 | callback: args[2], 352 | multi: args[3] 353 | }); 354 | } 355 | } else if (method === 'attach') { 356 | return this; 357 | } else { 358 | methods[method].call(this, { 359 | type: MODIFY 360 | }); 361 | } 362 | }, 363 | 364 | // onText. Usage: onText([match,] callback, multi); 365 | onText: function () { 366 | if (this.length === 0) { 367 | return this; 368 | } 369 | 370 | var args = Array.prototype.slice.call(arguments), 371 | method; 372 | 373 | if (methods[args[0]]) { 374 | method = args.shift(); 375 | } else { 376 | method = 'attach'; 377 | } 378 | 379 | if (typeof (args[0]) === 'function') { 380 | return methods[method].call(this, { 381 | type: TEXT, 382 | callback: args[0], 383 | multi: args[1] 384 | }); 385 | } else if (typeof (args[0]) === 'string' || args[0] instanceof RegExp && typeof (args[1]) === 'function') { 386 | return methods[method].call(this, { 387 | type: TEXT, 388 | conditions: args[0], 389 | callback: args[1], 390 | multi: args[2] 391 | }); 392 | } else if (method === 'attach') { 393 | return this; 394 | } else { 395 | methods[method].call(this, { 396 | type: TEXT 397 | }); 398 | } 399 | }, 400 | 401 | // onText. Usage: onMutate(type, ...); 402 | onMutate: function () { 403 | var args = Array.prototype.slice.call(arguments), 404 | type = args.shift(), 405 | typeMethod = 'on' + type.charAt(0).toUpperCase() + type.slice(1).toLowerCase(); 406 | if ($.fn[typeMethod]) $.fn[typeMethod].apply(this, args); 407 | } 408 | }); 409 | // Use $.onCreate as a shortcut to onCreate at the document level. 410 | $.onCreate = function (selector, callback, multi) { 411 | $(document).onCreate(selector, callback, multi); 412 | }; 413 | })); -------------------------------------------------------------------------------- /src/jquery.onmutate.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):"undefined"!=typeof exports?module.exports=t:t(jQuery)}(function(t,e){function s(t){var i;this.observe=function(a,n){e?(i=i||new e(t),i.observe(a,n)):i=setInterval(t,50)},this.disconnect=function(){i&&(e?i.disconnect():clearInterval(i))}}function c(e,a,n){this.callback=e,this.conditions=a,this.multi=n||!1,this.cbid=i++,this.processed=t([])}function l(e){var i={};return e.attributes&&t.each(e.attributes,function(t,e){i[e.name]=e.value}),i}function f(e,i){var a=[];return t.each(e,function(t,e){("undefined"==typeof i[t]||e!==i[t])&&a.push({attributeName:t})}),t.each(i,function(t){"undefined"==typeof e[t]&&a.push({attributeName:t})}),a}function o(t,e,i){var a=t.attributeName,n=e.attr(a),r=i.conditions||!1,s=r?r[0]:!1,c=r&&r[1]?r[1]:!1;return!s||s.indexOf(a)>-1?c?n.search(c)>=0:!0:!1}t||(t=t.fn.jquery?t:jQuery),e||(e=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver||!1);var i=0,a="create",n="modify",r="text",u={attach:function(i){var u=i.type,h=i.callback,d=i.multi||!1,p=i.conditions;this.data("onMutate")||this.data("onMutate",{create:{callbacks:[],ignore:!1},modify:{callbacks:[],ignore:!1},text:{callbacks:[],ignore:!1}});var b=this.data("onMutate")[u],y=b.callbacks,v=new c(h,p,d);y.unshift(v),e||u!==n||(b.attributeMap=l(this[0])),u===r&&(b.text=this.text());var k,m,g=this,x=b.mutcallback=b.mutcallback||function(i){if(y=b.callbacks,!b.ignore){b.ignore=!0;var s=u===a?t(p,g):g;if(s.length>0){var c,h;for(u===n&&(c=l(g[0])),u===r&&(h=g.text()),k=y.length-1;k>=0;k--){var d=y[k].processed,v=s.not(d);if(u===n){m=!1,e||(i=f(c,b.attributeMap));for(var x=0;x-1:!0)}v.length>0&&(u===a||m)&&(u===a&&(d=d.add(v)),y[k].processed=d,y[k].callback.call(g,v),y[k].multi||y.splice(k,1))}u===n&&(b.attributeMap=l(g[0])),u===r&&(b.text=g.text())}if(b.ignore&&setTimeout(function(){b.ignore=!1},1),0===y.length){if(!M)return!0;M.disconnect()}}};if(u===a&&x())return this;var w,M=b.observer=b.observer||new s(x);switch(u){case a:w={childList:!0,subtree:!0};break;case n:w={attributes:!0},p&&(w.attributeFilter=p[0].split(" "));break;case r:w={childList:!0,characterData:!0,subtree:!0}}return M.observe(g[0],w),this},detach:function(t){if(!this.data("onMutate"))return this;var e=t.type,i=this.data("onMutate")[e],a=i.callbacks,n=t.callback;if(n)for(var r=a.length-1;r>=0;r--)a[r].callback.prototype.cbid===n.prototype.cbid&&a.splice(r,r+1);else i.callbacks=[];return a.length||i.observer.disconnect(),this}};t.fn.extend({onCreate:function(){var t=Array.prototype.slice.call(arguments);return"string"!=typeof t[0]?this:"detach"===t[0]?u.detach.call(this,{type:a,callback:t[1]}):"string"==typeof t[0]&&"function"==typeof t[1]?u.attach.call(this,{type:a,conditions:t[0],callback:t[1],multi:t[2]}):this},onModify:function(){var e,t=Array.prototype.slice.call(arguments);if(e=u[t[0]]?t.shift():"attach","function"==typeof t[0])return u[e].call(this,{type:n,callback:t[0],multi:t[1]});if("string"==typeof t[0]){if("function"==typeof t[1])return u[e].call(this,{type:n,conditions:[t[0]],callback:t[1],multi:t[2]});if("string"==typeof t[1]||t[1]instanceof RegExp)return u[e].call(this,{type:n,conditions:[t[0],t[1]],callback:t[2],multi:t[3]})}else{if("attach"===e)return this;u[e].call(this,{type:n})}},onText:function(){if(0===this.length)return this;var e,t=Array.prototype.slice.call(arguments);return e=u[t[0]]?t.shift():"attach","function"==typeof t[0]?u[e].call(this,{type:r,callback:t[0],multi:t[1]}):"string"==typeof t[0]||t[0]instanceof RegExp&&"function"==typeof t[1]?u[e].call(this,{type:r,conditions:t[0],callback:t[1],multi:t[2]}):"attach"===e?this:void u[e].call(this,{type:r})}}),t.onCreate=function(e,i,a){t(document).onCreate(e,i,a)}}); --------------------------------------------------------------------------------