├── README.md ├── gator.js ├── gator.min.js ├── package.json └── plugins ├── gator-legacy.js └── gator-mouse.js /README.md: -------------------------------------------------------------------------------- 1 | # Gator 2 | 3 | Gator is a Javascript event delegation library. 4 | 5 | It is around **800** bytes when gzipped and minified and has no external dependencies. 6 | 7 | ## Browser Support 8 | 9 | Out of the box Gator works in 10 | - Chrome 11 | - Safari 5+ 12 | - Firefox 3.6+ 13 | - Internet Explorer 9+ 14 | 15 | The ``legacy.js`` plugin adds support for 16 | - Safari < 5 17 | - Firefox < 3.6 18 | - Internet Explorer 6, 7, 8 19 | 20 | *When using the legacy plugin only single classes, single ids, and single tag names are supported for selectors* 21 | 22 | ## Getting Started 23 | 24 | 1. Include gator on your page before the closing ```` tag 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | 2. Add some events 31 | 32 | ```html 33 | 51 | ``` 52 | 53 | ## Documentation 54 | 55 | Full documentation is available at http://craig.is/riding/gators 56 | -------------------------------------------------------------------------------- /gator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Craig Campbell 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * GATOR.JS 17 | * Simple Event Delegation 18 | * 19 | * @version 1.2.4 20 | * 21 | * Compatible with IE 9+, FF 3.6+, Safari 5+, Chrome 22 | * 23 | * Include legacy.js for compatibility with older browsers 24 | * 25 | * .-._ _ _ _ _ _ _ _ _ 26 | * .-''-.__.-'00 '-' ' ' ' ' ' ' ' '-. 27 | * '.___ ' . .--_'-' '-' '-' _'-' '._ 28 | * V: V 'vv-' '_ '. .' _..' '.'. 29 | * '=.____.=_.--' :_.__.__:_ '. : : 30 | * (((____.-' '-. / : : 31 | * (((-'\ .' / 32 | * _____..' .' 33 | * '-._____.-' 34 | */ 35 | (function() { 36 | var _matcher, 37 | _level = 0, 38 | _id = 0, 39 | _handlers = {}, 40 | _gatorInstances = {}; 41 | 42 | function _addEvent(gator, type, callback) { 43 | 44 | // blur and focus do not bubble up but if you use event capturing 45 | // then you will get them 46 | var useCapture = type == 'blur' || type == 'focus'; 47 | gator.element.addEventListener(type, callback, useCapture); 48 | } 49 | 50 | function _cancel(e) { 51 | e.preventDefault(); 52 | e.stopPropagation(); 53 | } 54 | 55 | /** 56 | * returns function to use for determining if an element 57 | * matches a query selector 58 | * 59 | * @returns {Function} 60 | */ 61 | function _getMatcher(element) { 62 | if (_matcher) { 63 | return _matcher; 64 | } 65 | 66 | if (element.matches) { 67 | _matcher = element.matches; 68 | return _matcher; 69 | } 70 | 71 | if (element.webkitMatchesSelector) { 72 | _matcher = element.webkitMatchesSelector; 73 | return _matcher; 74 | } 75 | 76 | if (element.mozMatchesSelector) { 77 | _matcher = element.mozMatchesSelector; 78 | return _matcher; 79 | } 80 | 81 | if (element.msMatchesSelector) { 82 | _matcher = element.msMatchesSelector; 83 | return _matcher; 84 | } 85 | 86 | if (element.oMatchesSelector) { 87 | _matcher = element.oMatchesSelector; 88 | return _matcher; 89 | } 90 | 91 | // if it doesn't match a native browser method 92 | // fall back to the gator function 93 | _matcher = Gator.matchesSelector; 94 | return _matcher; 95 | } 96 | 97 | /** 98 | * determines if the specified element matches a given selector 99 | * 100 | * @param {Node} element - the element to compare against the selector 101 | * @param {string} selector 102 | * @param {Node} boundElement - the element the listener was attached to 103 | * @returns {void|Node} 104 | */ 105 | function _matchesSelector(element, selector, boundElement) { 106 | 107 | // no selector means this event was bound directly to this element 108 | if (selector == '_root') { 109 | return boundElement; 110 | } 111 | 112 | // if we have moved up to the element you bound the event to 113 | // then we have come too far 114 | if (element === boundElement) { 115 | return; 116 | } 117 | 118 | // if this is a match then we are done! 119 | if (_getMatcher(element).call(element, selector)) { 120 | return element; 121 | } 122 | 123 | // if this element did not match but has a parent we should try 124 | // going up the tree to see if any of the parent elements match 125 | // for example if you are looking for a click on an tag but there 126 | // is a inside of the a tag that it is the target, 127 | // it should still work 128 | if (element.parentNode) { 129 | _level++; 130 | return _matchesSelector(element.parentNode, selector, boundElement); 131 | } 132 | } 133 | 134 | function _addHandler(gator, event, selector, callback) { 135 | if (!_handlers[gator.id]) { 136 | _handlers[gator.id] = {}; 137 | } 138 | 139 | if (!_handlers[gator.id][event]) { 140 | _handlers[gator.id][event] = {}; 141 | } 142 | 143 | if (!_handlers[gator.id][event][selector]) { 144 | _handlers[gator.id][event][selector] = []; 145 | } 146 | 147 | _handlers[gator.id][event][selector].push(callback); 148 | } 149 | 150 | function _removeHandler(gator, event, selector, callback) { 151 | 152 | // if there are no events tied to this element at all 153 | // then don't do anything 154 | if (!_handlers[gator.id]) { 155 | return; 156 | } 157 | 158 | // if there is no event type specified then remove all events 159 | // example: Gator(element).off() 160 | if (!event) { 161 | for (var type in _handlers[gator.id]) { 162 | if (_handlers[gator.id].hasOwnProperty(type)) { 163 | _handlers[gator.id][type] = {}; 164 | } 165 | } 166 | return; 167 | } 168 | 169 | // if no callback or selector is specified remove all events of this type 170 | // example: Gator(element).off('click') 171 | if (!callback && !selector) { 172 | _handlers[gator.id][event] = {}; 173 | return; 174 | } 175 | 176 | // if a selector is specified but no callback remove all events 177 | // for this selector 178 | // example: Gator(element).off('click', '.sub-element') 179 | if (!callback) { 180 | delete _handlers[gator.id][event][selector]; 181 | return; 182 | } 183 | 184 | // if we have specified an event type, selector, and callback then we 185 | // need to make sure there are callbacks tied to this selector to 186 | // begin with. if there aren't then we can stop here 187 | if (!_handlers[gator.id][event][selector]) { 188 | return; 189 | } 190 | 191 | // if there are then loop through all the callbacks and if we find 192 | // one that matches remove it from the array 193 | for (var i = 0; i < _handlers[gator.id][event][selector].length; i++) { 194 | if (_handlers[gator.id][event][selector][i] === callback) { 195 | _handlers[gator.id][event][selector].splice(i, 1); 196 | break; 197 | } 198 | } 199 | } 200 | 201 | function _handleEvent(id, e, type) { 202 | if (!_handlers[id][type]) { 203 | return; 204 | } 205 | 206 | var target = e.target || e.srcElement, 207 | selector, 208 | match, 209 | matches = {}, 210 | i = 0, 211 | j = 0; 212 | 213 | // find all events that match 214 | _level = 0; 215 | for (selector in _handlers[id][type]) { 216 | if (_handlers[id][type].hasOwnProperty(selector)) { 217 | match = _matchesSelector(target, selector, _gatorInstances[id].element); 218 | 219 | if (match && Gator.matchesEvent(type, _gatorInstances[id].element, match, selector == '_root', e)) { 220 | _level++; 221 | _handlers[id][type][selector].match = match; 222 | matches[_level] = _handlers[id][type][selector]; 223 | } 224 | } 225 | } 226 | 227 | // stopPropagation() fails to set cancelBubble to true in Webkit 228 | // @see http://code.google.com/p/chromium/issues/detail?id=162270 229 | e.stopPropagation = function() { 230 | e.cancelBubble = true; 231 | }; 232 | 233 | for (i = 0; i <= _level; i++) { 234 | if (matches[i]) { 235 | for (j = 0; j < matches[i].length; j++) { 236 | if (matches[i][j].call(matches[i].match, e) === false) { 237 | Gator.cancel(e); 238 | return; 239 | } 240 | 241 | if (e.cancelBubble) { 242 | return; 243 | } 244 | } 245 | } 246 | } 247 | } 248 | 249 | /** 250 | * binds the specified events to the element 251 | * 252 | * @param {string|Array} events 253 | * @param {string} selector 254 | * @param {Function} callback 255 | * @param {boolean=} remove 256 | * @returns {Object} 257 | */ 258 | function _bind(events, selector, callback, remove) { 259 | 260 | // fail silently if you pass null or undefined as an alement 261 | // in the Gator constructor 262 | if (!this.element) { 263 | return; 264 | } 265 | 266 | if (!(events instanceof Array)) { 267 | events = [events]; 268 | } 269 | 270 | if (!callback && typeof(selector) == 'function') { 271 | callback = selector; 272 | selector = '_root'; 273 | } 274 | 275 | var id = this.id, 276 | i; 277 | 278 | function _getGlobalCallback(type) { 279 | return function(e) { 280 | _handleEvent(id, e, type); 281 | }; 282 | } 283 | 284 | for (i = 0; i < events.length; i++) { 285 | if (remove) { 286 | _removeHandler(this, events[i], selector, callback); 287 | continue; 288 | } 289 | 290 | if (!_handlers[id] || !_handlers[id][events[i]]) { 291 | Gator.addEvent(this, events[i], _getGlobalCallback(events[i])); 292 | } 293 | 294 | _addHandler(this, events[i], selector, callback); 295 | } 296 | 297 | return this; 298 | } 299 | 300 | /** 301 | * Gator object constructor 302 | * 303 | * @param {Node} element 304 | */ 305 | function Gator(element, id) { 306 | 307 | // called as function 308 | if (!(this instanceof Gator)) { 309 | // only keep one Gator instance per node to make sure that 310 | // we don't create a ton of new objects if you want to delegate 311 | // multiple events from the same node 312 | // 313 | // for example: Gator(document).on(... 314 | for (var key in _gatorInstances) { 315 | if (_gatorInstances[key].element === element) { 316 | return _gatorInstances[key]; 317 | } 318 | } 319 | 320 | _id++; 321 | _gatorInstances[_id] = new Gator(element, _id); 322 | 323 | return _gatorInstances[_id]; 324 | } 325 | 326 | this.element = element; 327 | this.id = id; 328 | } 329 | 330 | /** 331 | * adds an event 332 | * 333 | * @param {string|Array} events 334 | * @param {string} selector 335 | * @param {Function} callback 336 | * @returns {Object} 337 | */ 338 | Gator.prototype.on = function(events, selector, callback) { 339 | return _bind.call(this, events, selector, callback); 340 | }; 341 | 342 | /** 343 | * removes an event 344 | * 345 | * @param {string|Array} events 346 | * @param {string} selector 347 | * @param {Function} callback 348 | * @returns {Object} 349 | */ 350 | Gator.prototype.off = function(events, selector, callback) { 351 | return _bind.call(this, events, selector, callback, true); 352 | }; 353 | 354 | Gator.matchesSelector = function() {}; 355 | Gator.cancel = _cancel; 356 | Gator.addEvent = _addEvent; 357 | Gator.matchesEvent = function() { 358 | return true; 359 | }; 360 | 361 | if (typeof module !== 'undefined' && module.exports) { 362 | module.exports = Gator; 363 | } 364 | 365 | window.Gator = Gator; 366 | }) (); 367 | -------------------------------------------------------------------------------- /gator.min.js: -------------------------------------------------------------------------------- 1 | /* gator v1.2.4 craig.is/riding/gators */ 2 | (function(){function t(a){return k?k:a.matches?k=a.matches:a.webkitMatchesSelector?k=a.webkitMatchesSelector:a.mozMatchesSelector?k=a.mozMatchesSelector:a.msMatchesSelector?k=a.msMatchesSelector:a.oMatchesSelector?k=a.oMatchesSelector:k=e.matchesSelector}function q(a,b,c){if("_root"==b)return c;if(a!==c){if(t(a).call(a,b))return a;if(a.parentNode)return m++,q(a.parentNode,b,c)}}function u(a,b,c,e){d[a.id]||(d[a.id]={});d[a.id][b]||(d[a.id][b]={});d[a.id][b][c]||(d[a.id][b][c]=[]);d[a.id][b][c].push(e)} 3 | function v(a,b,c,e){if(d[a.id])if(!b)for(var f in d[a.id])d[a.id].hasOwnProperty(f)&&(d[a.id][f]={});else if(!e&&!c)d[a.id][b]={};else if(!e)delete d[a.id][b][c];else if(d[a.id][b][c])for(f=0;f -1; 38 | } 39 | 40 | // check for id 41 | if (selector.charAt(0) === '#') { 42 | return this.id === selector.slice(1); 43 | } 44 | 45 | // check for tag 46 | return this.tagName === selector.toUpperCase(); 47 | }; 48 | 49 | Gator.cancel = function(e) { 50 | if (e.preventDefault) { 51 | e.preventDefault(); 52 | } 53 | 54 | if (e.stopPropagation) { 55 | e.stopPropagation(); 56 | } 57 | 58 | // for IE 59 | e.returnValue = false; 60 | e.cancelBubble = true; 61 | }; 62 | }) (Gator); 63 | -------------------------------------------------------------------------------- /plugins/gator-mouse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this plugin adds support for mouseenter and mouseleave events 3 | */ 4 | (function(Gator) { 5 | var oldAddEvent = Gator.addEvent; 6 | 7 | function _isMouseEnterOrLeave(parent_element, match, root, e) { 8 | if (!e.relatedTarget) { 9 | return true; 10 | } 11 | 12 | if (root && parent_element !== match) { 13 | return false; 14 | } 15 | 16 | if (match === e.relatedTarget) { 17 | return false; 18 | } 19 | 20 | if (match.contains(e.relatedTarget)) { 21 | return false; 22 | } 23 | 24 | return true; 25 | } 26 | 27 | Gator.addEvent = function(gator, type, callback) { 28 | // if the type is mouseenter or mouseleave then 29 | // bind them as mouseover and mouseout 30 | if (type == 'mouseenter') { 31 | type = 'mouseover'; 32 | } 33 | 34 | if (type == 'mouseleave') { 35 | type = 'mouseout'; 36 | } 37 | 38 | oldAddEvent(gator, type, callback); 39 | }; 40 | 41 | Gator.matchesEvent = function(type, element, match, root, e) { 42 | if (type == 'mouseenter' || type == 'mouseleave') { 43 | return _isMouseEnterOrLeave(element, match, root, e); 44 | } 45 | 46 | return true; 47 | }; 48 | }) (Gator); 49 | --------------------------------------------------------------------------------