/** * Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work * of Simon Willison (see comments by Simon below). * * With enhancements by Daniel James, April 2006: * - New calling style (old style still supported). * - Use event _apply_ for old style Behaviour use (was 'load'). * - Use event _setup_ to apply a function only once to an element. * - start is now lazy - only called when register() is first called. * - Works on mac IE 5.2. * * For more information on the changes made by Daniel James, visit * http://groups.google.com/group/behaviour/browse_thread/thread/66a33461dcb2bd21/36678fcebe6bdff1#36678fcebe6bdff1 * * Description: * Uses css selectors to apply javascript behaviours to enable * unobtrusive javascript in html documents. * * Usage: * var myrules = { * 'b.someclass' : { * 'click' : function(){ * alert(this.innerHTML); * } * }, * '#someid u' : { * 'mouseover' : function(){ * this.innerHTML = "BLAH!"; * } * } * }; * * Behaviour.register(myrules); * Call Behaviour.apply() to re-apply the rules (if you update the * dom, etc). * * License: * This file is entirely BSD licensed. * * More information: * http://ripcord.co.nz/behaviour/ */ var Behaviour = { // Private variables _list: [], _started: false, register: function() { if (!Behaviour._started) Behaviour.start(); Behaviour._list[Behaviour._list.length] = arguments; }, start: function() { // By Dean Edwards: if (document.addEventListener) document.addEventListener("DOMContentLoaded", function(){Behaviour.onLoad();}, false); if (typeof window.onload != 'function') window.onload = Behaviour.onLoad; else { Behaviour._oldOnLoad = window.onload; window.onload = function() { Behaviour._oldOnLoad(); onLoad(); } } Behaviour._started = true; }, onLoad: function() { if (!this.applied) Behaviour.apply(); this.applied = true; }, apply: function() { for (var h=0; h < Behaviour._list.length; h++) Behaviour._applySheet(Behaviour._list[h]); }, // Private function _applySheet: function(sheet) { switch (sheet.length) { case 1: for (var selector in sheet[0]) Behaviour._applyBehaviour(selector, sheet[0][selector]); break; case 2: Behaviour._applyBehaviour(sheet[0], sheet[1]); break; case 3: Behaviour._attachEvent(sheet[0], sheet[1], sheet[2]); break; default: // Do nothing } }, // Private function _applyBehaviour: function(selector, behaviour) { switch (typeof (behaviour)) { case "function": Behaviour._attachEvent(selector, '_apply_', behaviour); break; case "object": for (var event in behaviour) Behaviour._attachEvent(selector, event, behaviour[event]); break; default: // Do nothing } }, // Private function _attachEvent: function(selector, event, callback) { var elements = document.getElementsBySelector(selector); for (var i=0; i < elements.length; ++ i) { switch (event) { case '_setup_': if (!elements[i].called_setup) { elements[i].called_setup = true; callback(elements[i]); } break; case '_apply_': callback(elements[i]); break; default: elements[i]['on'+event] = callback; } } } }; /** * The following code is Copyright (C) Simon Willison 2004. * * document.getElementsBySelector(selector) * - returns an array of element objects from the current document * matching the CSS selector. Selectors can contain element names, * class names and ids and can be nested. For example: * * elements = document.getElementsBySelector('div#main p a.external') * * Will return an array of all 'a' elements with 'external' in their * class attribute that are contained inside 'p' elements that are * contained inside the 'div' element which has id="main" * * New in version 0.4: Support for CSS2 and CSS3 attribute selectors: * See http://www.w3.org/TR/css3-selectors/#attribute-selectors * * Version 0.4 - Simon Willison, March 25th 2003 * -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, IE 6, IE 5 on Windows * -- Opera 7 fails */ function getAllChildren(e) { // Returns all children of element. Workaround required for IE5/Windows. Ugh. return e.all ? e.all : e.getElementsByTagName('*'); }; document.getElementsBySelector = function(selector) { // Attempt to fail gracefully in lesser browsers if (!document.getElementsByTagName) return []; // Split selector in to tokens var tokens = selector.split(' '); var currentContext = [document]; for (var i=0; i < tokens.length; i++) { token = tokens[i].replace(/^\s+/, '').replace(/\s+$/, ''); if (token.indexOf('#') > - 1) { // Token is an ID selector var bits = token.split('#'); var tagName = bits[0]; var id = bits[1]; var element = document.getElementById(id); // Fix by Daniel Frechette, 2006-04-17 - Test if element found before continuing. if (!element || (tagName && tagName != element.nodeName.toLowerCase())) return []; // Set currentContext to contain just this element currentContext = [element]; continue; // Skip to next token } if (token.indexOf('.') > - 1) { // Token contains a class selector var bits = token.split('.'); var tagName = bits[0]; var className = bits[1]; if (!tagName) tagName = '*'; // Get elements matching tag, filter them for class selector var found = []; for (var h=0; h < currentContext.length; h++) { var elements = (tagName == '*' ? getAllChildren(currentContext[h]) : currentContext[h].getElementsByTagName(tagName)); for (var j=0; j < elements.length; j++) found.push(elements[j]); } currentContext = []; for (var k=0; k < found.length; k++) { if (found[k].className && found[k].className.match(new RegExp('\\b' + className + '\\b'))) currentContext.push(found[k]); } continue; // Skip to next token } // Code to deal with attribute selectors if (token.match(/^([\w_]*)\[([\w_]+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { var tagName = RegExp.$1; var attrName = RegExp.$2; var attrOperator = RegExp.$3; var attrValue = RegExp.$4; if (!tagName) tagName = '*'; // Grab all of the tagName elements within current context var found = []; for (var h=0; h < currentContext.length; h++) { var elements = (tagName == '*' ? getAllChildren(currentContext[h]) : currentContext[h].getElementsByTagName(tagName)); for (var j=0; j < elements.length; j++) found.push(elements[j]); } var checker; // This regular expression will be used to filter the elements switch (attrOperator) { case '=': // Equality checker = new RegExp('^' + attrValue + '$'); break; case '~': // Match one of space seperated words checker = new RegExp('\\b' + attrValue + '\\b'); break; case '|': // Match start with value followed by optional hyphen checker = new RegExp('^' + attrValue + '-?'); break; case '^': // Match starts with value checker = new RegExp('^' + attrValue); break; case '$': // Match ends with value - fails with "Warning" in Opera 7 checker = new RegExp(attrValue + '$'); break; case '*': // Match contains value checker = new RegExp(attrValue); break; default: // Just test for existence of attribute checker = /./; } currentContext = []; for (var k=0; k < found.length; k++) { if (found[k].getAttribute(attrName) && found[k].getAttribute(attrName).match(checker)) currentContext.push(found[k]); } // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue); continue; // Skip to next token } if (!currentContext[0]) return []; // Fix by Daniel Frechette, 2006-04-13 - Must return a value. // If we get here, token is JUST an element (not a class or ID selector) tagName = token; var found = []; for (var h=0; h < currentContext.length; h++) { var elements = currentContext[h].getElementsByTagName(tagName); for (var j=0; j < elements.length; j++) found.push(elements[j]); } currentContext = found; } return currentContext; }; /* That revolting regular expression explained /^([\w_]+)\[([\w_]+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ \------/ \-------/\-------------/ \-------/ | | | | | | | The value | | ~,|,^,$,* or = | Attribute Tag */