├── ruby_libs └── init.rb ├── tests ├── test_helpers.js ├── notifications.js ├── test.css ├── fluid.html ├── bubbles.html └── cookies.html ├── src ├── drivers │ ├── bubbles.js │ └── fluid.js ├── cookies.js └── ssbx.js ├── Rakefile ├── README.textile ├── ssbx.js └── lib ├── unittest.js └── prototype.js /ruby_libs/init.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'find' 3 | require 'ftools' 4 | -------------------------------------------------------------------------------- /tests/test_helpers.js: -------------------------------------------------------------------------------- 1 | function include(file) { 2 | var script = document.createElement("script"); 3 | script.setAttribute('src', file); 4 | $$('head').first().insert(script); 5 | } -------------------------------------------------------------------------------- /src/drivers/bubbles.js: -------------------------------------------------------------------------------- 1 | SSBXBase.Drivers.Bubbles = Class.create({ 2 | initialize: function() { 3 | if (SSBXBase.Debug) { 4 | SSB.console.init('info'); 5 | } 6 | }, 7 | 8 | /* Options: message, title, unique_id */ 9 | notify: function(options) { 10 | /* concat these until there's something more intelligent I can do with Bubbles */ 11 | var text = options['title'] + ' ' + options['message']; 12 | return SSB.simpleNotify(text); 13 | }, 14 | 15 | log: function(message) { 16 | SSB.console.debug(message); 17 | } 18 | }); -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'ruby_libs/init' 2 | 3 | namespace :ssbx do 4 | desc "Compiles all of the source files into ssbx.js" 5 | task :compile do 6 | source = '' 7 | Find.find('src/') do |path| 8 | if path.match /\.js/ 9 | file_name = path.split('/').last 10 | source << "/* START #{file_name} */\n" 11 | source << File.read(path) 12 | source << "\n/* END #{file_name} */\n" 13 | end 14 | end 15 | File.open('ssbx.js', 'w') { |f| f.write(source) } 16 | end 17 | 18 | desc "Runs the tests" 19 | task :test do 20 | end 21 | end -------------------------------------------------------------------------------- /src/drivers/fluid.js: -------------------------------------------------------------------------------- 1 | SSBXBase.Drivers.Fluid = Class.create({ 2 | initialize: function() { 3 | }, 4 | 5 | /* Options: message, title, unique_id */ 6 | notify: function(options) { 7 | window.fluid.showGrowlNotification({ 8 | title: options['title'], 9 | description: options['message'], 10 | priority: 1, 11 | sticky: false, 12 | identifier: options['unique_id'] 13 | }); 14 | }, 15 | 16 | setDockBadge: function(count) { 17 | window.fluid.dockBadge = count; 18 | }, 19 | 20 | log: function(message) { 21 | window.console.log(message) 22 | } 23 | }); -------------------------------------------------------------------------------- /tests/notifications.js: -------------------------------------------------------------------------------- 1 | var notificationTests = { 2 | setup: function() { 3 | }, 4 | 5 | teardown: function() { 6 | SSBXBase.Internal.Cookies.destroy(SSBXBase.CookieName) 7 | }, 8 | 9 | testNotify: function() { 10 | SSBX.notify({ message: 'This is a message', title: 'Hello' }) 11 | }, 12 | 13 | testNotifyOnce: function() { with(this) { 14 | SSBX.notifyOnce({ unique_id: 1, message: 'This should be displayed once', title: 'Hello Once' }) 15 | wait(1000, function() { 16 | SSBX.notifyOnce({ unique_id: 1, message: 'This should be displayed once', title: 'Hello Once' }) 17 | }) 18 | }} 19 | } 20 | -------------------------------------------------------------------------------- /src/cookies.js: -------------------------------------------------------------------------------- 1 | SSBXBase.Internal.Cookies = { 2 | save: function(name, value, days, path) { 3 | var expires = ''; 4 | path = typeof path == 'undefined' ? '/' : path; 5 | 6 | if (days) { 7 | var date = new Date(); 8 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 9 | expires = "; expires=" + date.toGMTString(); 10 | } 11 | 12 | if (name && value) { 13 | document.cookie = name + '=' + escape(value) + expires + '; path=' + path; 14 | } 15 | }, 16 | 17 | find: function(name) { 18 | var matches = document.cookie.match(name + '=([^;]*)'); 19 | 20 | if (matches && matches.length == 2) { 21 | return unescape(matches[1]); 22 | } 23 | }, 24 | 25 | destroy: function(name) { 26 | this.save(name, ' ', -1); 27 | } 28 | }; -------------------------------------------------------------------------------- /tests/test.css: -------------------------------------------------------------------------------- 1 | body { background-color: white; font-family: "Helvetica Neue", Helvetica, sans-serif; padding: 0 4em 2em 4em; margin: 0; font-size: 14px; } 2 | 3 | h1 { font-family: "Helvetica Neue", Helvetica, sans-serif; font-weight: bold; color: #411; } 4 | h2 { color: #292; font-family: "Trebuchet MS", Helvetica, sans-serif; font-size: 2em; margin: 0; padding: 5px 0; letter-spacing: -2px; } 5 | h3 { color: #141; margin: 10px 0 4px 0; padding: 0; font-size: normal} 6 | p { margin: 10px 0 0 0; padding: 0; } 7 | h1 img { margin: 0 0 0 0; padding: 0 0 0 20px; clear: none;} 8 | a { color: #333; text-decoration: none; background-color: #ffc; } 9 | a:hover { background-color: #333; color: white; text-decoration: none; } 10 | 11 | h2.page_title { color: #ff3092; text-align: left;} 12 | 13 | #Busybox { background-color: #000; color: #fff; } 14 | #HelpContainer { background-color: #000; color: #fff; } 15 | 16 | #Logo { float: right; } -------------------------------------------------------------------------------- /tests/fluid.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | SSBX Fluid Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

SSBX Fluid Tests

20 |

21 | Tests for notifications. 22 |

23 | 24 | 25 |
26 | 27 | 28 | 41 | 42 | -------------------------------------------------------------------------------- /tests/bubbles.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | SSBX Bubbles Tests 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |

SSBX Bubbles Tests

20 |

21 | Tests for notifications. 22 |

23 | 24 | 25 |
26 | 27 | 28 | 41 | 42 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h2. Introduction 2 | 3 | SSBX provides a stupidly simple API for supporting Single Site Browsers (SSB) in your website. 4 | 5 | The current version is a basic implementation to test out the feasibility of the concept, and explore how to make a consistent unified API across platforms. 6 | 7 | h3. Examples 8 | 9 |

10 | if (SSBX.isAvailable()) {
11 |   SSBX.log('hello');
12 |   SSBX.notify({ message: 'This is a message', title: 'Title' });
13 |   SSBX.notifyOnce({ message: 'This is a message', title: 'Title', unique_id: 5});
14 |   SSBX.setDockBadge(5);
15 |   SSBX.log(SSBX.availableDriver);
16 | }
17 | 
18 | 19 | You can also only run when there's a compatible SSB available like this: 20 | 21 |

22 | SSBX.run(function() {
23 |   SSBX.log('hello');
24 | }
25 | 
26 | 27 | h2. Requirements 28 | 29 | SSBX requires "Prototype":http://prototypejs.org. 30 | 31 | h3. Supported SSBs 32 | 33 | * Windows: "Bubbles":http://bubbleshq.com/api 34 | * Mac OS: "Fluid":http://fluidapp.com/ 35 | 36 | h3. Notification API 37 | 38 | Show a notification (requires Growl in Mac OS). Title is optional. 39 | 40 | SSBX.notify({ message: 'This is a message', title: 'Title' }) 41 | 42 | Show a notification and log it so it doesn't get displayed again. Logs to a cookie: 43 | 44 | SSBX.notifyOnce({ unique_id: 1, message: 'This is a message', title: 'Title' }) 45 | 46 | SSBX expects a unique integer ID for messages to be handled this way. 47 | 48 | h3. Dock badge icons 49 | 50 | Only available in Mac OS (until someone offers a Windows equivalent): 51 | 52 | SSBX.setDockBadge(3) 53 | 54 | h3. Console logging 55 | 56 | To make it easier to debug your JavaScript from within an SSB you can use unified console logging: 57 | 58 | SSBX.log('Message') 59 | 60 | h3. Capabilities 61 | 62 | * Test if the SSB is available: SSBX.isAvailable() 63 | * Check which SSB is active: SSBX.availableDriver 64 | 65 | h3. Drag and drop 66 | 67 | h2. Running tests 68 | 69 | Run tests inside the appropriate SSB. There's tests for Bubbles and Fluid. 70 | 71 | h2. Library style 72 | 73 | I've added semicolons to line endings to make compressing/obscuring the library easier (if you do that sort of thing). -------------------------------------------------------------------------------- /tests/cookies.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | SSBX Cookies Test 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

SSBX Cookies Test

18 |

19 | Tests for SSBXBase.Internal.Cookies 20 |

21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 85 | 86 | -------------------------------------------------------------------------------- /src/ssbx.js: -------------------------------------------------------------------------------- 1 | var SSBXBase = { 2 | Version: '0.1', 3 | Debug: true, 4 | CookieName: 'ssbx_cookies', 5 | 6 | Supported: { 7 | Fluid: function() { return !!window.fluid }, 8 | Bubbles: function() { return (typeof(SSB) != 'undefined') } 9 | }, 10 | 11 | API: {}, 12 | Internal: {}, 13 | Drivers: {} 14 | }; 15 | 16 | SSBXBase.Internal = { 17 | Cookies: {}, 18 | 19 | findDriver: function() { 20 | var ssb = $H(SSBXBase.Supported).find(function(ssb) { 21 | return ssb[1]() 22 | }) 23 | 24 | if (ssb) { 25 | return ssb[0] 26 | } 27 | }, 28 | 29 | logNotification: function(unique_id) { 30 | // Log message to cookie 31 | var cookie = this.Cookies.find(SSBXBase.CookieName); 32 | 33 | if (Object.isUndefined(cookie)) { 34 | cookie = unique_id; 35 | } else { 36 | cookie = cookie + ',' + unique_id; 37 | } 38 | 39 | this.Cookies.save(SSBXBase.CookieName, cookie, 365); 40 | }, 41 | 42 | displayedNotification: function(unique_id) { 43 | var cookie = this.Cookies.find(SSBXBase.CookieName) 44 | unique_id = parseInt(unique_id) 45 | 46 | if (Object.isUndefined(cookie)) { 47 | return false 48 | } 49 | 50 | var unique_ids = $A(cookie.split(',')) 51 | 52 | return unique_ids.find(function(logged_id) { 53 | if (parseInt(logged_id) == unique_id) { 54 | return true 55 | } 56 | }) 57 | }, 58 | 59 | // Logging uses console.log by default 60 | log: function(message) { 61 | console.log(message) 62 | } 63 | }; 64 | 65 | SSBXBase.API = Class.create({ 66 | initialize: function() { 67 | this.internal = SSBXBase.Internal; 68 | var delegate = this.internal.findDriver(); 69 | 70 | if (delegate) { 71 | this.delegate = eval('new SSBXBase.Drivers.' + delegate); 72 | 73 | // Intended for the public interface 74 | this.availableDriver = delegate; 75 | } 76 | }, 77 | 78 | isAvailable: function() { 79 | return !!this.delegate; 80 | }, 81 | 82 | run: function(callback) { 83 | if (this.isAvailable()) { 84 | callback() 85 | } 86 | }, 87 | 88 | // Only impleemnt the notify method 89 | notifyOnce: function(options) { 90 | // Display if it hasn't been logged 91 | if (!this.internal.displayedNotification(options['unique_id'])) { 92 | // Log the message 93 | this.internal.logNotification(options['unique_id']); 94 | 95 | return this.delegate.notify(options); 96 | } 97 | }, 98 | 99 | // Implement these methods to add drivers in SSBXBase.Drivers.YourDriver 100 | notify: function(options) { 101 | return this.delegate.notify(options); 102 | }, 103 | 104 | setDockBadge: function(count) { 105 | return this.delegate.setDockBadge ? this.delegate.setDockBadge(count) : false 106 | }, 107 | 108 | // Logging only gets run when Debug is set to true 109 | log: function(message) { 110 | if (SSBXBase.Debug) { 111 | return this.delegate.log ? this.delegate.log(message) : SSBXBase.Internal.log(message) 112 | } 113 | } 114 | }); 115 | 116 | // Create the public SSBX API object 117 | document.observe('dom:loaded', function() { 118 | SSBX = new SSBXBase.API; 119 | }); 120 | 121 | -------------------------------------------------------------------------------- /ssbx.js: -------------------------------------------------------------------------------- 1 | /* START ssbx.js */ 2 | var SSBXBase = { 3 | Version: '0.1', 4 | Debug: true, 5 | CookieName: 'ssbx_cookies', 6 | 7 | Supported: { 8 | Fluid: function() { return !!window.fluid }, 9 | Bubbles: function() { return (typeof(SSB) != 'undefined') } 10 | }, 11 | 12 | API: {}, 13 | Internal: {}, 14 | Drivers: {} 15 | }; 16 | 17 | SSBXBase.Internal = { 18 | Cookies: {}, 19 | 20 | findDriver: function() { 21 | var ssb = $H(SSBXBase.Supported).find(function(ssb) { 22 | return ssb[1]() 23 | }) 24 | 25 | if (ssb) { 26 | return ssb[0] 27 | } 28 | }, 29 | 30 | logNotification: function(unique_id) { 31 | // Log message to cookie 32 | var cookie = this.Cookies.find(SSBXBase.CookieName); 33 | 34 | if (Object.isUndefined(cookie)) { 35 | cookie = unique_id; 36 | } else { 37 | cookie = cookie + ',' + unique_id; 38 | } 39 | 40 | this.Cookies.save(SSBXBase.CookieName, cookie, 365); 41 | }, 42 | 43 | displayedNotification: function(unique_id) { 44 | var cookie = this.Cookies.find(SSBXBase.CookieName) 45 | unique_id = parseInt(unique_id) 46 | 47 | if (Object.isUndefined(cookie)) { 48 | return false 49 | } 50 | 51 | var unique_ids = $A(cookie.split(',')) 52 | 53 | return unique_ids.find(function(logged_id) { 54 | if (parseInt(logged_id) == unique_id) { 55 | return true 56 | } 57 | }) 58 | }, 59 | 60 | // Logging uses console.log by default 61 | log: function(message) { 62 | console.log(message) 63 | } 64 | }; 65 | 66 | SSBXBase.API = Class.create({ 67 | initialize: function() { 68 | this.internal = SSBXBase.Internal; 69 | var delegate = this.internal.findDriver(); 70 | 71 | if (delegate) { 72 | this.delegate = eval('new SSBXBase.Drivers.' + delegate); 73 | 74 | // Intended for the public interface 75 | this.availableDriver = delegate; 76 | } 77 | }, 78 | 79 | isAvailable: function() { 80 | return !!this.delegate; 81 | }, 82 | 83 | run: function(callback) { 84 | if (this.isAvailable()) { 85 | callback() 86 | } 87 | }, 88 | 89 | // Only impleemnt the notify method 90 | notifyOnce: function(options) { 91 | // Display if it hasn't been logged 92 | if (!this.internal.displayedNotification(options['unique_id'])) { 93 | // Log the message 94 | this.internal.logNotification(options['unique_id']); 95 | 96 | return this.delegate.notify(options); 97 | } 98 | }, 99 | 100 | // Implement these methods to add drivers in SSBXBase.Drivers.YourDriver 101 | notify: function(options) { 102 | return this.delegate.notify(options); 103 | }, 104 | 105 | setDockBadge: function(count) { 106 | return this.delegate.setDockBadge ? this.delegate.setDockBadge(count) : false 107 | }, 108 | 109 | // Logging only gets run when Debug is set to true 110 | log: function(message) { 111 | if (SSBXBase.Debug) { 112 | return this.delegate.log ? this.delegate.log(message) : SSBXBase.Internal.log(message) 113 | } 114 | } 115 | }); 116 | 117 | // Create the public SSBX API object 118 | document.observe('dom:loaded', function() { 119 | SSBX = new SSBXBase.API; 120 | }); 121 | 122 | 123 | /* END ssbx.js */ 124 | /* START fluid.js */ 125 | SSBXBase.Drivers.Fluid = Class.create({ 126 | initialize: function() { 127 | }, 128 | 129 | /* Options: message, title, unique_id */ 130 | notify: function(options) { 131 | window.fluid.showGrowlNotification({ 132 | title: options['title'], 133 | description: options['message'], 134 | priority: 1, 135 | sticky: false, 136 | identifier: options['unique_id'] 137 | }); 138 | }, 139 | 140 | setDockBadge: function(count) { 141 | window.fluid.dockBadge = count; 142 | }, 143 | 144 | log: function(message) { 145 | window.console.log(message) 146 | } 147 | }); 148 | /* END fluid.js */ 149 | /* START bubbles.js */ 150 | SSBXBase.Drivers.Bubbles = Class.create({ 151 | initialize: function() { 152 | if (SSBXBase.Debug) { 153 | SSB.console.init('info'); 154 | } 155 | }, 156 | 157 | /* Options: message, title, unique_id */ 158 | notify: function(options) { 159 | /* concat these until there's something more intelligent I can do with Bubbles */ 160 | var text = options['title'] + ' ' + options['message']; 161 | return SSB.simpleNotify(text); 162 | }, 163 | 164 | log: function(message) { 165 | SSB.console.debug(message); 166 | } 167 | }); 168 | /* END bubbles.js */ 169 | /* START cookies.js */ 170 | SSBXBase.Internal.Cookies = { 171 | save: function(name, value, days, path) { 172 | var expires = ''; 173 | path = typeof path == 'undefined' ? '/' : path; 174 | 175 | if (days) { 176 | var date = new Date(); 177 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 178 | expires = "; expires=" + date.toGMTString(); 179 | } 180 | 181 | if (name && value) { 182 | document.cookie = name + '=' + escape(value) + expires + '; path=' + path; 183 | } 184 | }, 185 | 186 | find: function(name) { 187 | var matches = document.cookie.match(name + '=([^;]*)'); 188 | 189 | if (matches && matches.length == 2) { 190 | return unescape(matches[1]); 191 | } 192 | }, 193 | 194 | destroy: function(name) { 195 | this.save(name, ' ', -1); 196 | } 197 | }; 198 | /* END cookies.js */ 199 | -------------------------------------------------------------------------------- /lib/unittest.js: -------------------------------------------------------------------------------- 1 | // script.aculo.us unittest.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007 2 | 3 | // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4 | // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) 5 | // (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/) 6 | // 7 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 8 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 9 | 10 | // experimental, Firefox-only 11 | Event.simulateMouse = function(element, eventName) { 12 | var options = Object.extend({ 13 | pointerX: 0, 14 | pointerY: 0, 15 | buttons: 0, 16 | ctrlKey: false, 17 | altKey: false, 18 | shiftKey: false, 19 | metaKey: false 20 | }, arguments[2] || {}); 21 | var oEvent = document.createEvent("MouseEvents"); 22 | oEvent.initMouseEvent(eventName, true, true, document.defaultView, 23 | options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 24 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); 25 | 26 | if(this.mark) Element.remove(this.mark); 27 | this.mark = document.createElement('div'); 28 | this.mark.appendChild(document.createTextNode(" ")); 29 | document.body.appendChild(this.mark); 30 | this.mark.style.position = 'absolute'; 31 | this.mark.style.top = options.pointerY + "px"; 32 | this.mark.style.left = options.pointerX + "px"; 33 | this.mark.style.width = "5px"; 34 | this.mark.style.height = "5px;"; 35 | this.mark.style.borderTop = "1px solid red;" 36 | this.mark.style.borderLeft = "1px solid red;" 37 | 38 | if(this.step) 39 | alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); 40 | 41 | $(element).dispatchEvent(oEvent); 42 | }; 43 | 44 | // Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2. 45 | // You need to downgrade to 1.0.4 for now to get this working 46 | // See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much 47 | Event.simulateKey = function(element, eventName) { 48 | var options = Object.extend({ 49 | ctrlKey: false, 50 | altKey: false, 51 | shiftKey: false, 52 | metaKey: false, 53 | keyCode: 0, 54 | charCode: 0 55 | }, arguments[2] || {}); 56 | 57 | var oEvent = document.createEvent("KeyEvents"); 58 | oEvent.initKeyEvent(eventName, true, true, window, 59 | options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 60 | options.keyCode, options.charCode ); 61 | $(element).dispatchEvent(oEvent); 62 | }; 63 | 64 | Event.simulateKeys = function(element, command) { 65 | for(var i=0; i' + 116 | '' + 117 | '' + 118 | '' + 119 | '
StatusTestMessage
'; 120 | this.logsummary = $('logsummary') 121 | this.loglines = $('loglines'); 122 | }, 123 | _toHTML: function(txt) { 124 | return txt.escapeHTML().replace(/\n/g,"
"); 125 | }, 126 | addLinksToResults: function(){ 127 | $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log 128 | td.title = "Run only this test" 129 | Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); 130 | }); 131 | $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log 132 | td.title = "Run all tests" 133 | Event.observe(td, 'click', function(){ window.location.search = "";}); 134 | }); 135 | } 136 | } 137 | 138 | Test.Unit.Runner = Class.create(); 139 | Test.Unit.Runner.prototype = { 140 | initialize: function(testcases) { 141 | this.options = Object.extend({ 142 | testLog: 'testlog' 143 | }, arguments[1] || {}); 144 | this.options.resultsURL = this.parseResultsURLQueryParameter(); 145 | this.options.tests = this.parseTestsQueryParameter(); 146 | if (this.options.testLog) { 147 | this.options.testLog = $(this.options.testLog) || null; 148 | } 149 | if(this.options.tests) { 150 | this.tests = []; 151 | for(var i = 0; i < this.options.tests.length; i++) { 152 | if(/^test/.test(this.options.tests[i])) { 153 | this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); 154 | } 155 | } 156 | } else { 157 | if (this.options.test) { 158 | this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; 159 | } else { 160 | this.tests = []; 161 | for(var testcase in testcases) { 162 | if(/^test/.test(testcase)) { 163 | this.tests.push( 164 | new Test.Unit.Testcase( 165 | this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 166 | testcases[testcase], testcases["setup"], testcases["teardown"] 167 | )); 168 | } 169 | } 170 | } 171 | } 172 | this.currentTest = 0; 173 | this.logger = new Test.Unit.Logger(this.options.testLog); 174 | setTimeout(this.runTests.bind(this), 1000); 175 | }, 176 | parseResultsURLQueryParameter: function() { 177 | return window.location.search.parseQuery()["resultsURL"]; 178 | }, 179 | parseTestsQueryParameter: function(){ 180 | if (window.location.search.parseQuery()["tests"]){ 181 | return window.location.search.parseQuery()["tests"].split(','); 182 | }; 183 | }, 184 | // Returns: 185 | // "ERROR" if there was an error, 186 | // "FAILURE" if there was a failure, or 187 | // "SUCCESS" if there was neither 188 | getResult: function() { 189 | var hasFailure = false; 190 | for(var i=0;i 0) { 192 | return "ERROR"; 193 | } 194 | if (this.tests[i].failures > 0) { 195 | hasFailure = true; 196 | } 197 | } 198 | if (hasFailure) { 199 | return "FAILURE"; 200 | } else { 201 | return "SUCCESS"; 202 | } 203 | }, 204 | postResults: function() { 205 | if (this.options.resultsURL) { 206 | new Ajax.Request(this.options.resultsURL, 207 | { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); 208 | } 209 | }, 210 | runTests: function() { 211 | var test = this.tests[this.currentTest]; 212 | if (!test) { 213 | // finished! 214 | this.postResults(); 215 | this.logger.summary(this.summary()); 216 | return; 217 | } 218 | if(!test.isWaiting) { 219 | this.logger.start(test.name); 220 | } 221 | test.run(); 222 | if(test.isWaiting) { 223 | this.logger.message("Waiting for " + test.timeToWait + "ms"); 224 | setTimeout(this.runTests.bind(this), test.timeToWait || 1000); 225 | } else { 226 | this.logger.finish(test.status(), test.summary()); 227 | this.currentTest++; 228 | // tail recursive, hopefully the browser will skip the stackframe 229 | this.runTests(); 230 | } 231 | }, 232 | summary: function() { 233 | var assertions = 0; 234 | var failures = 0; 235 | var errors = 0; 236 | var messages = []; 237 | for(var i=0;i 0) return 'failed'; 282 | if (this.errors > 0) return 'error'; 283 | return 'passed'; 284 | }, 285 | assert: function(expression) { 286 | var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; 287 | try { expression ? this.pass() : 288 | this.fail(message); } 289 | catch(e) { this.error(e); } 290 | }, 291 | assertEqual: function(expected, actual) { 292 | var message = arguments[2] || "assertEqual"; 293 | try { (expected == actual) ? this.pass() : 294 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 295 | '", actual "' + Test.Unit.inspect(actual) + '"'); } 296 | catch(e) { this.error(e); } 297 | }, 298 | assertInspect: function(expected, actual) { 299 | var message = arguments[2] || "assertInspect"; 300 | try { (expected == actual.inspect()) ? this.pass() : 301 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 302 | '", actual "' + Test.Unit.inspect(actual) + '"'); } 303 | catch(e) { this.error(e); } 304 | }, 305 | assertEnumEqual: function(expected, actual) { 306 | var message = arguments[2] || "assertEnumEqual"; 307 | try { $A(expected).length == $A(actual).length && 308 | expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? 309 | this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 310 | ', actual ' + Test.Unit.inspect(actual)); } 311 | catch(e) { this.error(e); } 312 | }, 313 | assertNotEqual: function(expected, actual) { 314 | var message = arguments[2] || "assertNotEqual"; 315 | try { (expected != actual) ? this.pass() : 316 | this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } 317 | catch(e) { this.error(e); } 318 | }, 319 | assertIdentical: function(expected, actual) { 320 | var message = arguments[2] || "assertIdentical"; 321 | try { (expected === actual) ? this.pass() : 322 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 323 | '", actual "' + Test.Unit.inspect(actual) + '"'); } 324 | catch(e) { this.error(e); } 325 | }, 326 | assertNotIdentical: function(expected, actual) { 327 | var message = arguments[2] || "assertNotIdentical"; 328 | try { !(expected === actual) ? this.pass() : 329 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 330 | '", actual "' + Test.Unit.inspect(actual) + '"'); } 331 | catch(e) { this.error(e); } 332 | }, 333 | assertNull: function(obj) { 334 | var message = arguments[1] || 'assertNull' 335 | try { (obj==null) ? this.pass() : 336 | this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } 337 | catch(e) { this.error(e); } 338 | }, 339 | assertMatch: function(expected, actual) { 340 | var message = arguments[2] || 'assertMatch'; 341 | var regex = new RegExp(expected); 342 | try { (regex.exec(actual)) ? this.pass() : 343 | this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } 344 | catch(e) { this.error(e); } 345 | }, 346 | assertHidden: function(element) { 347 | var message = arguments[1] || 'assertHidden'; 348 | this.assertEqual("none", element.style.display, message); 349 | }, 350 | assertNotNull: function(object) { 351 | var message = arguments[1] || 'assertNotNull'; 352 | this.assert(object != null, message); 353 | }, 354 | assertType: function(expected, actual) { 355 | var message = arguments[2] || 'assertType'; 356 | try { 357 | (actual.constructor == expected) ? this.pass() : 358 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 359 | '", actual "' + (actual.constructor) + '"'); } 360 | catch(e) { this.error(e); } 361 | }, 362 | assertNotOfType: function(expected, actual) { 363 | var message = arguments[2] || 'assertNotOfType'; 364 | try { 365 | (actual.constructor != expected) ? this.pass() : 366 | this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 367 | '", actual "' + (actual.constructor) + '"'); } 368 | catch(e) { this.error(e); } 369 | }, 370 | assertInstanceOf: function(expected, actual) { 371 | var message = arguments[2] || 'assertInstanceOf'; 372 | try { 373 | (actual instanceof expected) ? this.pass() : 374 | this.fail(message + ": object was not an instance of the expected type"); } 375 | catch(e) { this.error(e); } 376 | }, 377 | assertNotInstanceOf: function(expected, actual) { 378 | var message = arguments[2] || 'assertNotInstanceOf'; 379 | try { 380 | !(actual instanceof expected) ? this.pass() : 381 | this.fail(message + ": object was an instance of the not expected type"); } 382 | catch(e) { this.error(e); } 383 | }, 384 | assertRespondsTo: function(method, obj) { 385 | var message = arguments[2] || 'assertRespondsTo'; 386 | try { 387 | (obj[method] && typeof obj[method] == 'function') ? this.pass() : 388 | this.fail(message + ": object doesn't respond to [" + method + "]"); } 389 | catch(e) { this.error(e); } 390 | }, 391 | assertReturnsTrue: function(method, obj) { 392 | var message = arguments[2] || 'assertReturnsTrue'; 393 | try { 394 | var m = obj[method]; 395 | if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; 396 | m() ? this.pass() : 397 | this.fail(message + ": method returned false"); } 398 | catch(e) { this.error(e); } 399 | }, 400 | assertReturnsFalse: function(method, obj) { 401 | var message = arguments[2] || 'assertReturnsFalse'; 402 | try { 403 | var m = obj[method]; 404 | if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; 405 | !m() ? this.pass() : 406 | this.fail(message + ": method returned true"); } 407 | catch(e) { this.error(e); } 408 | }, 409 | assertRaise: function(exceptionName, method) { 410 | var message = arguments[2] || 'assertRaise'; 411 | try { 412 | method(); 413 | this.fail(message + ": exception expected but none was raised"); } 414 | catch(e) { 415 | ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 416 | } 417 | }, 418 | assertElementsMatch: function() { 419 | var expressions = $A(arguments), elements = $A(expressions.shift()); 420 | if (elements.length != expressions.length) { 421 | this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); 422 | return false; 423 | } 424 | elements.zip(expressions).all(function(pair, index) { 425 | var element = $(pair.first()), expression = pair.last(); 426 | if (element.match(expression)) return true; 427 | this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); 428 | }.bind(this)) && this.pass(); 429 | }, 430 | assertElementMatches: function(element, expression) { 431 | this.assertElementsMatch([element], expression); 432 | }, 433 | benchmark: function(operation, iterations) { 434 | var startAt = new Date(); 435 | (iterations || 1).times(operation); 436 | var timeTaken = ((new Date())-startAt); 437 | this.info((arguments[2] || 'Operation') + ' finished ' + 438 | iterations + ' iterations in ' + (timeTaken/1000)+'s' ); 439 | return timeTaken; 440 | }, 441 | _isVisible: function(element) { 442 | element = $(element); 443 | if(!element.parentNode) return true; 444 | this.assertNotNull(element); 445 | if(element.style && Element.getStyle(element, 'display') == 'none') 446 | return false; 447 | 448 | return this._isVisible(element.parentNode); 449 | }, 450 | assertNotVisible: function(element) { 451 | this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); 452 | }, 453 | assertVisible: function(element) { 454 | this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); 455 | }, 456 | benchmark: function(operation, iterations) { 457 | var startAt = new Date(); 458 | (iterations || 1).times(operation); 459 | var timeTaken = ((new Date())-startAt); 460 | this.info((arguments[2] || 'Operation') + ' finished ' + 461 | iterations + ' iterations in ' + (timeTaken/1000)+'s' ); 462 | return timeTaken; 463 | } 464 | } 465 | 466 | Test.Unit.Testcase = Class.create(); 467 | Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { 468 | initialize: function(name, test, setup, teardown) { 469 | Test.Unit.Assertions.prototype.initialize.bind(this)(); 470 | this.name = name; 471 | 472 | if(typeof test == 'string') { 473 | test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); 474 | test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); 475 | this.test = function() { 476 | eval('with(this){'+test+'}'); 477 | } 478 | } else { 479 | this.test = test || function() {}; 480 | } 481 | 482 | this.setup = setup || function() {}; 483 | this.teardown = teardown || function() {}; 484 | this.isWaiting = false; 485 | this.timeToWait = 1000; 486 | }, 487 | wait: function(time, nextPart) { 488 | this.isWaiting = true; 489 | this.test = nextPart; 490 | this.timeToWait = time; 491 | }, 492 | run: function() { 493 | try { 494 | try { 495 | if (!this.isWaiting) this.setup.bind(this)(); 496 | this.isWaiting = false; 497 | this.test.bind(this)(); 498 | } finally { 499 | if(!this.isWaiting) { 500 | this.teardown.bind(this)(); 501 | } 502 | } 503 | } 504 | catch(e) { this.error(e); } 505 | } 506 | }); 507 | 508 | // *EXPERIMENTAL* BDD-style testing to please non-technical folk 509 | // This draws many ideas from RSpec http://rspec.rubyforge.org/ 510 | 511 | Test.setupBDDExtensionMethods = function(){ 512 | var METHODMAP = { 513 | shouldEqual: 'assertEqual', 514 | shouldNotEqual: 'assertNotEqual', 515 | shouldEqualEnum: 'assertEnumEqual', 516 | shouldBeA: 'assertType', 517 | shouldNotBeA: 'assertNotOfType', 518 | shouldBeAn: 'assertType', 519 | shouldNotBeAn: 'assertNotOfType', 520 | shouldBeNull: 'assertNull', 521 | shouldNotBeNull: 'assertNotNull', 522 | 523 | shouldBe: 'assertReturnsTrue', 524 | shouldNotBe: 'assertReturnsFalse', 525 | shouldRespondTo: 'assertRespondsTo' 526 | }; 527 | var makeAssertion = function(assertion, args, object) { 528 | this[assertion].apply(this,(args || []).concat([object])); 529 | } 530 | 531 | Test.BDDMethods = {}; 532 | $H(METHODMAP).each(function(pair) { 533 | Test.BDDMethods[pair.key] = function() { 534 | var args = $A(arguments); 535 | var scope = args.shift(); 536 | makeAssertion.apply(scope, [pair.value, args, this]); }; 537 | }); 538 | 539 | [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each( 540 | function(p){ Object.extend(p, Test.BDDMethods) } 541 | ); 542 | } 543 | 544 | Test.context = function(name, spec, log){ 545 | Test.setupBDDExtensionMethods(); 546 | 547 | var compiledSpec = {}; 548 | var titles = {}; 549 | for(specName in spec) { 550 | switch(specName){ 551 | case "setup": 552 | case "teardown": 553 | compiledSpec[specName] = spec[specName]; 554 | break; 555 | default: 556 | var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); 557 | var body = spec[specName].toString().split('\n').slice(1); 558 | if(/^\{/.test(body[0])) body = body.slice(1); 559 | body.pop(); 560 | body = body.map(function(statement){ 561 | return statement.strip() 562 | }); 563 | compiledSpec[testName] = body.join('\n'); 564 | titles[testName] = specName; 565 | } 566 | } 567 | new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); 568 | }; -------------------------------------------------------------------------------- /lib/prototype.js: -------------------------------------------------------------------------------- 1 | /* Prototype JavaScript framework, version 1.6.0.1 2 | * (c) 2005-2007 Sam Stephenson 3 | * 4 | * Prototype is freely distributable under the terms of an MIT-style license. 5 | * For details, see the Prototype web site: http://www.prototypejs.org/ 6 | * 7 | *--------------------------------------------------------------------------*/ 8 | 9 | var Prototype = { 10 | Version: '1.6.0.1', 11 | 12 | Browser: { 13 | IE: !!(window.attachEvent && !window.opera), 14 | Opera: !!window.opera, 15 | WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, 16 | Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, 17 | MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) 18 | }, 19 | 20 | BrowserFeatures: { 21 | XPath: !!document.evaluate, 22 | ElementExtensions: !!window.HTMLElement, 23 | SpecificElementExtensions: 24 | document.createElement('div').__proto__ && 25 | document.createElement('div').__proto__ !== 26 | document.createElement('form').__proto__ 27 | }, 28 | 29 | ScriptFragment: ']*>([\\S\\s]*?)<\/script>', 30 | JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, 31 | 32 | emptyFunction: function() { }, 33 | K: function(x) { return x } 34 | }; 35 | 36 | if (Prototype.Browser.MobileSafari) 37 | Prototype.BrowserFeatures.SpecificElementExtensions = false; 38 | 39 | 40 | /* Based on Alex Arnell's inheritance implementation. */ 41 | var Class = { 42 | create: function() { 43 | var parent = null, properties = $A(arguments); 44 | if (Object.isFunction(properties[0])) 45 | parent = properties.shift(); 46 | 47 | function klass() { 48 | this.initialize.apply(this, arguments); 49 | } 50 | 51 | Object.extend(klass, Class.Methods); 52 | klass.superclass = parent; 53 | klass.subclasses = []; 54 | 55 | if (parent) { 56 | var subclass = function() { }; 57 | subclass.prototype = parent.prototype; 58 | klass.prototype = new subclass; 59 | parent.subclasses.push(klass); 60 | } 61 | 62 | for (var i = 0; i < properties.length; i++) 63 | klass.addMethods(properties[i]); 64 | 65 | if (!klass.prototype.initialize) 66 | klass.prototype.initialize = Prototype.emptyFunction; 67 | 68 | klass.prototype.constructor = klass; 69 | 70 | return klass; 71 | } 72 | }; 73 | 74 | Class.Methods = { 75 | addMethods: function(source) { 76 | var ancestor = this.superclass && this.superclass.prototype; 77 | var properties = Object.keys(source); 78 | 79 | if (!Object.keys({ toString: true }).length) 80 | properties.push("toString", "valueOf"); 81 | 82 | for (var i = 0, length = properties.length; i < length; i++) { 83 | var property = properties[i], value = source[property]; 84 | if (ancestor && Object.isFunction(value) && 85 | value.argumentNames().first() == "$super") { 86 | var method = value, value = Object.extend((function(m) { 87 | return function() { return ancestor[m].apply(this, arguments) }; 88 | })(property).wrap(method), { 89 | valueOf: function() { return method }, 90 | toString: function() { return method.toString() } 91 | }); 92 | } 93 | this.prototype[property] = value; 94 | } 95 | 96 | return this; 97 | } 98 | }; 99 | 100 | var Abstract = { }; 101 | 102 | Object.extend = function(destination, source) { 103 | for (var property in source) 104 | destination[property] = source[property]; 105 | return destination; 106 | }; 107 | 108 | Object.extend(Object, { 109 | inspect: function(object) { 110 | try { 111 | if (Object.isUndefined(object)) return 'undefined'; 112 | if (object === null) return 'null'; 113 | return object.inspect ? object.inspect() : object.toString(); 114 | } catch (e) { 115 | if (e instanceof RangeError) return '...'; 116 | throw e; 117 | } 118 | }, 119 | 120 | toJSON: function(object) { 121 | var type = typeof object; 122 | switch (type) { 123 | case 'undefined': 124 | case 'function': 125 | case 'unknown': return; 126 | case 'boolean': return object.toString(); 127 | } 128 | 129 | if (object === null) return 'null'; 130 | if (object.toJSON) return object.toJSON(); 131 | if (Object.isElement(object)) return; 132 | 133 | var results = []; 134 | for (var property in object) { 135 | var value = Object.toJSON(object[property]); 136 | if (!Object.isUndefined(value)) 137 | results.push(property.toJSON() + ': ' + value); 138 | } 139 | 140 | return '{' + results.join(', ') + '}'; 141 | }, 142 | 143 | toQueryString: function(object) { 144 | return $H(object).toQueryString(); 145 | }, 146 | 147 | toHTML: function(object) { 148 | return object && object.toHTML ? object.toHTML() : String.interpret(object); 149 | }, 150 | 151 | keys: function(object) { 152 | var keys = []; 153 | for (var property in object) 154 | keys.push(property); 155 | return keys; 156 | }, 157 | 158 | values: function(object) { 159 | var values = []; 160 | for (var property in object) 161 | values.push(object[property]); 162 | return values; 163 | }, 164 | 165 | clone: function(object) { 166 | return Object.extend({ }, object); 167 | }, 168 | 169 | isElement: function(object) { 170 | return object && object.nodeType == 1; 171 | }, 172 | 173 | isArray: function(object) { 174 | return object && object.constructor === Array; 175 | }, 176 | 177 | isHash: function(object) { 178 | return object instanceof Hash; 179 | }, 180 | 181 | isFunction: function(object) { 182 | return typeof object == "function"; 183 | }, 184 | 185 | isString: function(object) { 186 | return typeof object == "string"; 187 | }, 188 | 189 | isNumber: function(object) { 190 | return typeof object == "number"; 191 | }, 192 | 193 | isUndefined: function(object) { 194 | return typeof object == "undefined"; 195 | } 196 | }); 197 | 198 | Object.extend(Function.prototype, { 199 | argumentNames: function() { 200 | var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); 201 | return names.length == 1 && !names[0] ? [] : names; 202 | }, 203 | 204 | bind: function() { 205 | if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; 206 | var __method = this, args = $A(arguments), object = args.shift(); 207 | return function() { 208 | return __method.apply(object, args.concat($A(arguments))); 209 | } 210 | }, 211 | 212 | bindAsEventListener: function() { 213 | var __method = this, args = $A(arguments), object = args.shift(); 214 | return function(event) { 215 | return __method.apply(object, [event || window.event].concat(args)); 216 | } 217 | }, 218 | 219 | curry: function() { 220 | if (!arguments.length) return this; 221 | var __method = this, args = $A(arguments); 222 | return function() { 223 | return __method.apply(this, args.concat($A(arguments))); 224 | } 225 | }, 226 | 227 | delay: function() { 228 | var __method = this, args = $A(arguments), timeout = args.shift() * 1000; 229 | return window.setTimeout(function() { 230 | return __method.apply(__method, args); 231 | }, timeout); 232 | }, 233 | 234 | wrap: function(wrapper) { 235 | var __method = this; 236 | return function() { 237 | return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); 238 | } 239 | }, 240 | 241 | methodize: function() { 242 | if (this._methodized) return this._methodized; 243 | var __method = this; 244 | return this._methodized = function() { 245 | return __method.apply(null, [this].concat($A(arguments))); 246 | }; 247 | } 248 | }); 249 | 250 | Function.prototype.defer = Function.prototype.delay.curry(0.01); 251 | 252 | Date.prototype.toJSON = function() { 253 | return '"' + this.getUTCFullYear() + '-' + 254 | (this.getUTCMonth() + 1).toPaddedString(2) + '-' + 255 | this.getUTCDate().toPaddedString(2) + 'T' + 256 | this.getUTCHours().toPaddedString(2) + ':' + 257 | this.getUTCMinutes().toPaddedString(2) + ':' + 258 | this.getUTCSeconds().toPaddedString(2) + 'Z"'; 259 | }; 260 | 261 | var Try = { 262 | these: function() { 263 | var returnValue; 264 | 265 | for (var i = 0, length = arguments.length; i < length; i++) { 266 | var lambda = arguments[i]; 267 | try { 268 | returnValue = lambda(); 269 | break; 270 | } catch (e) { } 271 | } 272 | 273 | return returnValue; 274 | } 275 | }; 276 | 277 | RegExp.prototype.match = RegExp.prototype.test; 278 | 279 | RegExp.escape = function(str) { 280 | return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); 281 | }; 282 | 283 | /*--------------------------------------------------------------------------*/ 284 | 285 | var PeriodicalExecuter = Class.create({ 286 | initialize: function(callback, frequency) { 287 | this.callback = callback; 288 | this.frequency = frequency; 289 | this.currentlyExecuting = false; 290 | 291 | this.registerCallback(); 292 | }, 293 | 294 | registerCallback: function() { 295 | this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); 296 | }, 297 | 298 | execute: function() { 299 | this.callback(this); 300 | }, 301 | 302 | stop: function() { 303 | if (!this.timer) return; 304 | clearInterval(this.timer); 305 | this.timer = null; 306 | }, 307 | 308 | onTimerEvent: function() { 309 | if (!this.currentlyExecuting) { 310 | try { 311 | this.currentlyExecuting = true; 312 | this.execute(); 313 | } finally { 314 | this.currentlyExecuting = false; 315 | } 316 | } 317 | } 318 | }); 319 | Object.extend(String, { 320 | interpret: function(value) { 321 | return value == null ? '' : String(value); 322 | }, 323 | specialChar: { 324 | '\b': '\\b', 325 | '\t': '\\t', 326 | '\n': '\\n', 327 | '\f': '\\f', 328 | '\r': '\\r', 329 | '\\': '\\\\' 330 | } 331 | }); 332 | 333 | Object.extend(String.prototype, { 334 | gsub: function(pattern, replacement) { 335 | var result = '', source = this, match; 336 | replacement = arguments.callee.prepareReplacement(replacement); 337 | 338 | while (source.length > 0) { 339 | if (match = source.match(pattern)) { 340 | result += source.slice(0, match.index); 341 | result += String.interpret(replacement(match)); 342 | source = source.slice(match.index + match[0].length); 343 | } else { 344 | result += source, source = ''; 345 | } 346 | } 347 | return result; 348 | }, 349 | 350 | sub: function(pattern, replacement, count) { 351 | replacement = this.gsub.prepareReplacement(replacement); 352 | count = Object.isUndefined(count) ? 1 : count; 353 | 354 | return this.gsub(pattern, function(match) { 355 | if (--count < 0) return match[0]; 356 | return replacement(match); 357 | }); 358 | }, 359 | 360 | scan: function(pattern, iterator) { 361 | this.gsub(pattern, iterator); 362 | return String(this); 363 | }, 364 | 365 | truncate: function(length, truncation) { 366 | length = length || 30; 367 | truncation = Object.isUndefined(truncation) ? '...' : truncation; 368 | return this.length > length ? 369 | this.slice(0, length - truncation.length) + truncation : String(this); 370 | }, 371 | 372 | strip: function() { 373 | return this.replace(/^\s+/, '').replace(/\s+$/, ''); 374 | }, 375 | 376 | stripTags: function() { 377 | return this.replace(/<\/?[^>]+>/gi, ''); 378 | }, 379 | 380 | stripScripts: function() { 381 | return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); 382 | }, 383 | 384 | extractScripts: function() { 385 | var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); 386 | var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); 387 | return (this.match(matchAll) || []).map(function(scriptTag) { 388 | return (scriptTag.match(matchOne) || ['', ''])[1]; 389 | }); 390 | }, 391 | 392 | evalScripts: function() { 393 | return this.extractScripts().map(function(script) { return eval(script) }); 394 | }, 395 | 396 | escapeHTML: function() { 397 | var self = arguments.callee; 398 | self.text.data = this; 399 | return self.div.innerHTML; 400 | }, 401 | 402 | unescapeHTML: function() { 403 | var div = new Element('div'); 404 | div.innerHTML = this.stripTags(); 405 | return div.childNodes[0] ? (div.childNodes.length > 1 ? 406 | $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : 407 | div.childNodes[0].nodeValue) : ''; 408 | }, 409 | 410 | toQueryParams: function(separator) { 411 | var match = this.strip().match(/([^?#]*)(#.*)?$/); 412 | if (!match) return { }; 413 | 414 | return match[1].split(separator || '&').inject({ }, function(hash, pair) { 415 | if ((pair = pair.split('='))[0]) { 416 | var key = decodeURIComponent(pair.shift()); 417 | var value = pair.length > 1 ? pair.join('=') : pair[0]; 418 | if (value != undefined) value = decodeURIComponent(value); 419 | 420 | if (key in hash) { 421 | if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; 422 | hash[key].push(value); 423 | } 424 | else hash[key] = value; 425 | } 426 | return hash; 427 | }); 428 | }, 429 | 430 | toArray: function() { 431 | return this.split(''); 432 | }, 433 | 434 | succ: function() { 435 | return this.slice(0, this.length - 1) + 436 | String.fromCharCode(this.charCodeAt(this.length - 1) + 1); 437 | }, 438 | 439 | times: function(count) { 440 | return count < 1 ? '' : new Array(count + 1).join(this); 441 | }, 442 | 443 | camelize: function() { 444 | var parts = this.split('-'), len = parts.length; 445 | if (len == 1) return parts[0]; 446 | 447 | var camelized = this.charAt(0) == '-' 448 | ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) 449 | : parts[0]; 450 | 451 | for (var i = 1; i < len; i++) 452 | camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); 453 | 454 | return camelized; 455 | }, 456 | 457 | capitalize: function() { 458 | return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 459 | }, 460 | 461 | underscore: function() { 462 | return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); 463 | }, 464 | 465 | dasherize: function() { 466 | return this.gsub(/_/,'-'); 467 | }, 468 | 469 | inspect: function(useDoubleQuotes) { 470 | var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { 471 | var character = String.specialChar[match[0]]; 472 | return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); 473 | }); 474 | if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; 475 | return "'" + escapedString.replace(/'/g, '\\\'') + "'"; 476 | }, 477 | 478 | toJSON: function() { 479 | return this.inspect(true); 480 | }, 481 | 482 | unfilterJSON: function(filter) { 483 | return this.sub(filter || Prototype.JSONFilter, '#{1}'); 484 | }, 485 | 486 | isJSON: function() { 487 | var str = this; 488 | if (str.blank()) return false; 489 | str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); 490 | return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); 491 | }, 492 | 493 | evalJSON: function(sanitize) { 494 | var json = this.unfilterJSON(); 495 | try { 496 | if (!sanitize || json.isJSON()) return eval('(' + json + ')'); 497 | } catch (e) { } 498 | throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); 499 | }, 500 | 501 | include: function(pattern) { 502 | return this.indexOf(pattern) > -1; 503 | }, 504 | 505 | startsWith: function(pattern) { 506 | return this.indexOf(pattern) === 0; 507 | }, 508 | 509 | endsWith: function(pattern) { 510 | var d = this.length - pattern.length; 511 | return d >= 0 && this.lastIndexOf(pattern) === d; 512 | }, 513 | 514 | empty: function() { 515 | return this == ''; 516 | }, 517 | 518 | blank: function() { 519 | return /^\s*$/.test(this); 520 | }, 521 | 522 | interpolate: function(object, pattern) { 523 | return new Template(this, pattern).evaluate(object); 524 | } 525 | }); 526 | 527 | if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { 528 | escapeHTML: function() { 529 | return this.replace(/&/g,'&').replace(//g,'>'); 530 | }, 531 | unescapeHTML: function() { 532 | return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); 533 | } 534 | }); 535 | 536 | String.prototype.gsub.prepareReplacement = function(replacement) { 537 | if (Object.isFunction(replacement)) return replacement; 538 | var template = new Template(replacement); 539 | return function(match) { return template.evaluate(match) }; 540 | }; 541 | 542 | String.prototype.parseQuery = String.prototype.toQueryParams; 543 | 544 | Object.extend(String.prototype.escapeHTML, { 545 | div: document.createElement('div'), 546 | text: document.createTextNode('') 547 | }); 548 | 549 | with (String.prototype.escapeHTML) div.appendChild(text); 550 | 551 | var Template = Class.create({ 552 | initialize: function(template, pattern) { 553 | this.template = template.toString(); 554 | this.pattern = pattern || Template.Pattern; 555 | }, 556 | 557 | evaluate: function(object) { 558 | if (Object.isFunction(object.toTemplateReplacements)) 559 | object = object.toTemplateReplacements(); 560 | 561 | return this.template.gsub(this.pattern, function(match) { 562 | if (object == null) return ''; 563 | 564 | var before = match[1] || ''; 565 | if (before == '\\') return match[2]; 566 | 567 | var ctx = object, expr = match[3]; 568 | var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; 569 | match = pattern.exec(expr); 570 | if (match == null) return before; 571 | 572 | while (match != null) { 573 | var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; 574 | ctx = ctx[comp]; 575 | if (null == ctx || '' == match[3]) break; 576 | expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); 577 | match = pattern.exec(expr); 578 | } 579 | 580 | return before + String.interpret(ctx); 581 | }.bind(this)); 582 | } 583 | }); 584 | Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; 585 | 586 | var $break = { }; 587 | 588 | var Enumerable = { 589 | each: function(iterator, context) { 590 | var index = 0; 591 | iterator = iterator.bind(context); 592 | try { 593 | this._each(function(value) { 594 | iterator(value, index++); 595 | }); 596 | } catch (e) { 597 | if (e != $break) throw e; 598 | } 599 | return this; 600 | }, 601 | 602 | eachSlice: function(number, iterator, context) { 603 | iterator = iterator ? iterator.bind(context) : Prototype.K; 604 | var index = -number, slices = [], array = this.toArray(); 605 | while ((index += number) < array.length) 606 | slices.push(array.slice(index, index+number)); 607 | return slices.collect(iterator, context); 608 | }, 609 | 610 | all: function(iterator, context) { 611 | iterator = iterator ? iterator.bind(context) : Prototype.K; 612 | var result = true; 613 | this.each(function(value, index) { 614 | result = result && !!iterator(value, index); 615 | if (!result) throw $break; 616 | }); 617 | return result; 618 | }, 619 | 620 | any: function(iterator, context) { 621 | iterator = iterator ? iterator.bind(context) : Prototype.K; 622 | var result = false; 623 | this.each(function(value, index) { 624 | if (result = !!iterator(value, index)) 625 | throw $break; 626 | }); 627 | return result; 628 | }, 629 | 630 | collect: function(iterator, context) { 631 | iterator = iterator ? iterator.bind(context) : Prototype.K; 632 | var results = []; 633 | this.each(function(value, index) { 634 | results.push(iterator(value, index)); 635 | }); 636 | return results; 637 | }, 638 | 639 | detect: function(iterator, context) { 640 | iterator = iterator.bind(context); 641 | var result; 642 | this.each(function(value, index) { 643 | if (iterator(value, index)) { 644 | result = value; 645 | throw $break; 646 | } 647 | }); 648 | return result; 649 | }, 650 | 651 | findAll: function(iterator, context) { 652 | iterator = iterator.bind(context); 653 | var results = []; 654 | this.each(function(value, index) { 655 | if (iterator(value, index)) 656 | results.push(value); 657 | }); 658 | return results; 659 | }, 660 | 661 | grep: function(filter, iterator, context) { 662 | iterator = iterator ? iterator.bind(context) : Prototype.K; 663 | var results = []; 664 | 665 | if (Object.isString(filter)) 666 | filter = new RegExp(filter); 667 | 668 | this.each(function(value, index) { 669 | if (filter.match(value)) 670 | results.push(iterator(value, index)); 671 | }); 672 | return results; 673 | }, 674 | 675 | include: function(object) { 676 | if (Object.isFunction(this.indexOf)) 677 | if (this.indexOf(object) != -1) return true; 678 | 679 | var found = false; 680 | this.each(function(value) { 681 | if (value == object) { 682 | found = true; 683 | throw $break; 684 | } 685 | }); 686 | return found; 687 | }, 688 | 689 | inGroupsOf: function(number, fillWith) { 690 | fillWith = Object.isUndefined(fillWith) ? null : fillWith; 691 | return this.eachSlice(number, function(slice) { 692 | while(slice.length < number) slice.push(fillWith); 693 | return slice; 694 | }); 695 | }, 696 | 697 | inject: function(memo, iterator, context) { 698 | iterator = iterator.bind(context); 699 | this.each(function(value, index) { 700 | memo = iterator(memo, value, index); 701 | }); 702 | return memo; 703 | }, 704 | 705 | invoke: function(method) { 706 | var args = $A(arguments).slice(1); 707 | return this.map(function(value) { 708 | return value[method].apply(value, args); 709 | }); 710 | }, 711 | 712 | max: function(iterator, context) { 713 | iterator = iterator ? iterator.bind(context) : Prototype.K; 714 | var result; 715 | this.each(function(value, index) { 716 | value = iterator(value, index); 717 | if (result == null || value >= result) 718 | result = value; 719 | }); 720 | return result; 721 | }, 722 | 723 | min: function(iterator, context) { 724 | iterator = iterator ? iterator.bind(context) : Prototype.K; 725 | var result; 726 | this.each(function(value, index) { 727 | value = iterator(value, index); 728 | if (result == null || value < result) 729 | result = value; 730 | }); 731 | return result; 732 | }, 733 | 734 | partition: function(iterator, context) { 735 | iterator = iterator ? iterator.bind(context) : Prototype.K; 736 | var trues = [], falses = []; 737 | this.each(function(value, index) { 738 | (iterator(value, index) ? 739 | trues : falses).push(value); 740 | }); 741 | return [trues, falses]; 742 | }, 743 | 744 | pluck: function(property) { 745 | var results = []; 746 | this.each(function(value) { 747 | results.push(value[property]); 748 | }); 749 | return results; 750 | }, 751 | 752 | reject: function(iterator, context) { 753 | iterator = iterator.bind(context); 754 | var results = []; 755 | this.each(function(value, index) { 756 | if (!iterator(value, index)) 757 | results.push(value); 758 | }); 759 | return results; 760 | }, 761 | 762 | sortBy: function(iterator, context) { 763 | iterator = iterator.bind(context); 764 | return this.map(function(value, index) { 765 | return {value: value, criteria: iterator(value, index)}; 766 | }).sort(function(left, right) { 767 | var a = left.criteria, b = right.criteria; 768 | return a < b ? -1 : a > b ? 1 : 0; 769 | }).pluck('value'); 770 | }, 771 | 772 | toArray: function() { 773 | return this.map(); 774 | }, 775 | 776 | zip: function() { 777 | var iterator = Prototype.K, args = $A(arguments); 778 | if (Object.isFunction(args.last())) 779 | iterator = args.pop(); 780 | 781 | var collections = [this].concat(args).map($A); 782 | return this.map(function(value, index) { 783 | return iterator(collections.pluck(index)); 784 | }); 785 | }, 786 | 787 | size: function() { 788 | return this.toArray().length; 789 | }, 790 | 791 | inspect: function() { 792 | return '#'; 793 | } 794 | }; 795 | 796 | Object.extend(Enumerable, { 797 | map: Enumerable.collect, 798 | find: Enumerable.detect, 799 | select: Enumerable.findAll, 800 | filter: Enumerable.findAll, 801 | member: Enumerable.include, 802 | entries: Enumerable.toArray, 803 | every: Enumerable.all, 804 | some: Enumerable.any 805 | }); 806 | function $A(iterable) { 807 | if (!iterable) return []; 808 | if (iterable.toArray) return iterable.toArray(); 809 | var length = iterable.length, results = new Array(length); 810 | while (length--) results[length] = iterable[length]; 811 | return results; 812 | } 813 | 814 | if (Prototype.Browser.WebKit) { 815 | function $A(iterable) { 816 | if (!iterable) return []; 817 | if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && 818 | iterable.toArray) return iterable.toArray(); 819 | var length = iterable.length, results = new Array(length); 820 | while (length--) results[length] = iterable[length]; 821 | return results; 822 | } 823 | } 824 | 825 | Array.from = $A; 826 | 827 | Object.extend(Array.prototype, Enumerable); 828 | 829 | if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; 830 | 831 | Object.extend(Array.prototype, { 832 | _each: function(iterator) { 833 | for (var i = 0, length = this.length; i < length; i++) 834 | iterator(this[i]); 835 | }, 836 | 837 | clear: function() { 838 | this.length = 0; 839 | return this; 840 | }, 841 | 842 | first: function() { 843 | return this[0]; 844 | }, 845 | 846 | last: function() { 847 | return this[this.length - 1]; 848 | }, 849 | 850 | compact: function() { 851 | return this.select(function(value) { 852 | return value != null; 853 | }); 854 | }, 855 | 856 | flatten: function() { 857 | return this.inject([], function(array, value) { 858 | return array.concat(Object.isArray(value) ? 859 | value.flatten() : [value]); 860 | }); 861 | }, 862 | 863 | without: function() { 864 | var values = $A(arguments); 865 | return this.select(function(value) { 866 | return !values.include(value); 867 | }); 868 | }, 869 | 870 | reverse: function(inline) { 871 | return (inline !== false ? this : this.toArray())._reverse(); 872 | }, 873 | 874 | reduce: function() { 875 | return this.length > 1 ? this : this[0]; 876 | }, 877 | 878 | uniq: function(sorted) { 879 | return this.inject([], function(array, value, index) { 880 | if (0 == index || (sorted ? array.last() != value : !array.include(value))) 881 | array.push(value); 882 | return array; 883 | }); 884 | }, 885 | 886 | intersect: function(array) { 887 | return this.uniq().findAll(function(item) { 888 | return array.detect(function(value) { return item === value }); 889 | }); 890 | }, 891 | 892 | clone: function() { 893 | return [].concat(this); 894 | }, 895 | 896 | size: function() { 897 | return this.length; 898 | }, 899 | 900 | inspect: function() { 901 | return '[' + this.map(Object.inspect).join(', ') + ']'; 902 | }, 903 | 904 | toJSON: function() { 905 | var results = []; 906 | this.each(function(object) { 907 | var value = Object.toJSON(object); 908 | if (!Object.isUndefined(value)) results.push(value); 909 | }); 910 | return '[' + results.join(', ') + ']'; 911 | } 912 | }); 913 | 914 | // use native browser JS 1.6 implementation if available 915 | if (Object.isFunction(Array.prototype.forEach)) 916 | Array.prototype._each = Array.prototype.forEach; 917 | 918 | if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { 919 | i || (i = 0); 920 | var length = this.length; 921 | if (i < 0) i = length + i; 922 | for (; i < length; i++) 923 | if (this[i] === item) return i; 924 | return -1; 925 | }; 926 | 927 | if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { 928 | i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; 929 | var n = this.slice(0, i).reverse().indexOf(item); 930 | return (n < 0) ? n : i - n - 1; 931 | }; 932 | 933 | Array.prototype.toArray = Array.prototype.clone; 934 | 935 | function $w(string) { 936 | if (!Object.isString(string)) return []; 937 | string = string.strip(); 938 | return string ? string.split(/\s+/) : []; 939 | } 940 | 941 | if (Prototype.Browser.Opera){ 942 | Array.prototype.concat = function() { 943 | var array = []; 944 | for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); 945 | for (var i = 0, length = arguments.length; i < length; i++) { 946 | if (Object.isArray(arguments[i])) { 947 | for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) 948 | array.push(arguments[i][j]); 949 | } else { 950 | array.push(arguments[i]); 951 | } 952 | } 953 | return array; 954 | }; 955 | } 956 | Object.extend(Number.prototype, { 957 | toColorPart: function() { 958 | return this.toPaddedString(2, 16); 959 | }, 960 | 961 | succ: function() { 962 | return this + 1; 963 | }, 964 | 965 | times: function(iterator) { 966 | $R(0, this, true).each(iterator); 967 | return this; 968 | }, 969 | 970 | toPaddedString: function(length, radix) { 971 | var string = this.toString(radix || 10); 972 | return '0'.times(length - string.length) + string; 973 | }, 974 | 975 | toJSON: function() { 976 | return isFinite(this) ? this.toString() : 'null'; 977 | } 978 | }); 979 | 980 | $w('abs round ceil floor').each(function(method){ 981 | Number.prototype[method] = Math[method].methodize(); 982 | }); 983 | function $H(object) { 984 | return new Hash(object); 985 | }; 986 | 987 | var Hash = Class.create(Enumerable, (function() { 988 | 989 | function toQueryPair(key, value) { 990 | if (Object.isUndefined(value)) return key; 991 | return key + '=' + encodeURIComponent(String.interpret(value)); 992 | } 993 | 994 | return { 995 | initialize: function(object) { 996 | this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); 997 | }, 998 | 999 | _each: function(iterator) { 1000 | for (var key in this._object) { 1001 | var value = this._object[key], pair = [key, value]; 1002 | pair.key = key; 1003 | pair.value = value; 1004 | iterator(pair); 1005 | } 1006 | }, 1007 | 1008 | set: function(key, value) { 1009 | return this._object[key] = value; 1010 | }, 1011 | 1012 | get: function(key) { 1013 | return this._object[key]; 1014 | }, 1015 | 1016 | unset: function(key) { 1017 | var value = this._object[key]; 1018 | delete this._object[key]; 1019 | return value; 1020 | }, 1021 | 1022 | toObject: function() { 1023 | return Object.clone(this._object); 1024 | }, 1025 | 1026 | keys: function() { 1027 | return this.pluck('key'); 1028 | }, 1029 | 1030 | values: function() { 1031 | return this.pluck('value'); 1032 | }, 1033 | 1034 | index: function(value) { 1035 | var match = this.detect(function(pair) { 1036 | return pair.value === value; 1037 | }); 1038 | return match && match.key; 1039 | }, 1040 | 1041 | merge: function(object) { 1042 | return this.clone().update(object); 1043 | }, 1044 | 1045 | update: function(object) { 1046 | return new Hash(object).inject(this, function(result, pair) { 1047 | result.set(pair.key, pair.value); 1048 | return result; 1049 | }); 1050 | }, 1051 | 1052 | toQueryString: function() { 1053 | return this.map(function(pair) { 1054 | var key = encodeURIComponent(pair.key), values = pair.value; 1055 | 1056 | if (values && typeof values == 'object') { 1057 | if (Object.isArray(values)) 1058 | return values.map(toQueryPair.curry(key)).join('&'); 1059 | } 1060 | return toQueryPair(key, values); 1061 | }).join('&'); 1062 | }, 1063 | 1064 | inspect: function() { 1065 | return '#'; 1068 | }, 1069 | 1070 | toJSON: function() { 1071 | return Object.toJSON(this.toObject()); 1072 | }, 1073 | 1074 | clone: function() { 1075 | return new Hash(this); 1076 | } 1077 | } 1078 | })()); 1079 | 1080 | Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; 1081 | Hash.from = $H; 1082 | var ObjectRange = Class.create(Enumerable, { 1083 | initialize: function(start, end, exclusive) { 1084 | this.start = start; 1085 | this.end = end; 1086 | this.exclusive = exclusive; 1087 | }, 1088 | 1089 | _each: function(iterator) { 1090 | var value = this.start; 1091 | while (this.include(value)) { 1092 | iterator(value); 1093 | value = value.succ(); 1094 | } 1095 | }, 1096 | 1097 | include: function(value) { 1098 | if (value < this.start) 1099 | return false; 1100 | if (this.exclusive) 1101 | return value < this.end; 1102 | return value <= this.end; 1103 | } 1104 | }); 1105 | 1106 | var $R = function(start, end, exclusive) { 1107 | return new ObjectRange(start, end, exclusive); 1108 | }; 1109 | 1110 | var Ajax = { 1111 | getTransport: function() { 1112 | return Try.these( 1113 | function() {return new XMLHttpRequest()}, 1114 | function() {return new ActiveXObject('Msxml2.XMLHTTP')}, 1115 | function() {return new ActiveXObject('Microsoft.XMLHTTP')} 1116 | ) || false; 1117 | }, 1118 | 1119 | activeRequestCount: 0 1120 | }; 1121 | 1122 | Ajax.Responders = { 1123 | responders: [], 1124 | 1125 | _each: function(iterator) { 1126 | this.responders._each(iterator); 1127 | }, 1128 | 1129 | register: function(responder) { 1130 | if (!this.include(responder)) 1131 | this.responders.push(responder); 1132 | }, 1133 | 1134 | unregister: function(responder) { 1135 | this.responders = this.responders.without(responder); 1136 | }, 1137 | 1138 | dispatch: function(callback, request, transport, json) { 1139 | this.each(function(responder) { 1140 | if (Object.isFunction(responder[callback])) { 1141 | try { 1142 | responder[callback].apply(responder, [request, transport, json]); 1143 | } catch (e) { } 1144 | } 1145 | }); 1146 | } 1147 | }; 1148 | 1149 | Object.extend(Ajax.Responders, Enumerable); 1150 | 1151 | Ajax.Responders.register({ 1152 | onCreate: function() { Ajax.activeRequestCount++ }, 1153 | onComplete: function() { Ajax.activeRequestCount-- } 1154 | }); 1155 | 1156 | Ajax.Base = Class.create({ 1157 | initialize: function(options) { 1158 | this.options = { 1159 | method: 'post', 1160 | asynchronous: true, 1161 | contentType: 'application/x-www-form-urlencoded', 1162 | encoding: 'UTF-8', 1163 | parameters: '', 1164 | evalJSON: true, 1165 | evalJS: true 1166 | }; 1167 | Object.extend(this.options, options || { }); 1168 | 1169 | this.options.method = this.options.method.toLowerCase(); 1170 | 1171 | if (Object.isString(this.options.parameters)) 1172 | this.options.parameters = this.options.parameters.toQueryParams(); 1173 | else if (Object.isHash(this.options.parameters)) 1174 | this.options.parameters = this.options.parameters.toObject(); 1175 | } 1176 | }); 1177 | 1178 | Ajax.Request = Class.create(Ajax.Base, { 1179 | _complete: false, 1180 | 1181 | initialize: function($super, url, options) { 1182 | $super(options); 1183 | this.transport = Ajax.getTransport(); 1184 | this.request(url); 1185 | }, 1186 | 1187 | request: function(url) { 1188 | this.url = url; 1189 | this.method = this.options.method; 1190 | var params = Object.clone(this.options.parameters); 1191 | 1192 | if (!['get', 'post'].include(this.method)) { 1193 | // simulate other verbs over post 1194 | params['_method'] = this.method; 1195 | this.method = 'post'; 1196 | } 1197 | 1198 | this.parameters = params; 1199 | 1200 | if (params = Object.toQueryString(params)) { 1201 | // when GET, append parameters to URL 1202 | if (this.method == 'get') 1203 | this.url += (this.url.include('?') ? '&' : '?') + params; 1204 | else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) 1205 | params += '&_='; 1206 | } 1207 | 1208 | try { 1209 | var response = new Ajax.Response(this); 1210 | if (this.options.onCreate) this.options.onCreate(response); 1211 | Ajax.Responders.dispatch('onCreate', this, response); 1212 | 1213 | this.transport.open(this.method.toUpperCase(), this.url, 1214 | this.options.asynchronous); 1215 | 1216 | if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); 1217 | 1218 | this.transport.onreadystatechange = this.onStateChange.bind(this); 1219 | this.setRequestHeaders(); 1220 | 1221 | this.body = this.method == 'post' ? (this.options.postBody || params) : null; 1222 | this.transport.send(this.body); 1223 | 1224 | /* Force Firefox to handle ready state 4 for synchronous requests */ 1225 | if (!this.options.asynchronous && this.transport.overrideMimeType) 1226 | this.onStateChange(); 1227 | 1228 | } 1229 | catch (e) { 1230 | this.dispatchException(e); 1231 | } 1232 | }, 1233 | 1234 | onStateChange: function() { 1235 | var readyState = this.transport.readyState; 1236 | if (readyState > 1 && !((readyState == 4) && this._complete)) 1237 | this.respondToReadyState(this.transport.readyState); 1238 | }, 1239 | 1240 | setRequestHeaders: function() { 1241 | var headers = { 1242 | 'X-Requested-With': 'XMLHttpRequest', 1243 | 'X-Prototype-Version': Prototype.Version, 1244 | 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' 1245 | }; 1246 | 1247 | if (this.method == 'post') { 1248 | headers['Content-type'] = this.options.contentType + 1249 | (this.options.encoding ? '; charset=' + this.options.encoding : ''); 1250 | 1251 | /* Force "Connection: close" for older Mozilla browsers to work 1252 | * around a bug where XMLHttpRequest sends an incorrect 1253 | * Content-length header. See Mozilla Bugzilla #246651. 1254 | */ 1255 | if (this.transport.overrideMimeType && 1256 | (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) 1257 | headers['Connection'] = 'close'; 1258 | } 1259 | 1260 | // user-defined headers 1261 | if (typeof this.options.requestHeaders == 'object') { 1262 | var extras = this.options.requestHeaders; 1263 | 1264 | if (Object.isFunction(extras.push)) 1265 | for (var i = 0, length = extras.length; i < length; i += 2) 1266 | headers[extras[i]] = extras[i+1]; 1267 | else 1268 | $H(extras).each(function(pair) { headers[pair.key] = pair.value }); 1269 | } 1270 | 1271 | for (var name in headers) 1272 | this.transport.setRequestHeader(name, headers[name]); 1273 | }, 1274 | 1275 | success: function() { 1276 | var status = this.getStatus(); 1277 | return !status || (status >= 200 && status < 300); 1278 | }, 1279 | 1280 | getStatus: function() { 1281 | try { 1282 | return this.transport.status || 0; 1283 | } catch (e) { return 0 } 1284 | }, 1285 | 1286 | respondToReadyState: function(readyState) { 1287 | var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); 1288 | 1289 | if (state == 'Complete') { 1290 | try { 1291 | this._complete = true; 1292 | (this.options['on' + response.status] 1293 | || this.options['on' + (this.success() ? 'Success' : 'Failure')] 1294 | || Prototype.emptyFunction)(response, response.headerJSON); 1295 | } catch (e) { 1296 | this.dispatchException(e); 1297 | } 1298 | 1299 | var contentType = response.getHeader('Content-type'); 1300 | if (this.options.evalJS == 'force' 1301 | || (this.options.evalJS && contentType 1302 | && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) 1303 | this.evalResponse(); 1304 | } 1305 | 1306 | try { 1307 | (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); 1308 | Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); 1309 | } catch (e) { 1310 | this.dispatchException(e); 1311 | } 1312 | 1313 | if (state == 'Complete') { 1314 | // avoid memory leak in MSIE: clean up 1315 | this.transport.onreadystatechange = Prototype.emptyFunction; 1316 | } 1317 | }, 1318 | 1319 | getHeader: function(name) { 1320 | try { 1321 | return this.transport.getResponseHeader(name); 1322 | } catch (e) { return null } 1323 | }, 1324 | 1325 | evalResponse: function() { 1326 | try { 1327 | return eval((this.transport.responseText || '').unfilterJSON()); 1328 | } catch (e) { 1329 | this.dispatchException(e); 1330 | } 1331 | }, 1332 | 1333 | dispatchException: function(exception) { 1334 | (this.options.onException || Prototype.emptyFunction)(this, exception); 1335 | Ajax.Responders.dispatch('onException', this, exception); 1336 | } 1337 | }); 1338 | 1339 | Ajax.Request.Events = 1340 | ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; 1341 | 1342 | Ajax.Response = Class.create({ 1343 | initialize: function(request){ 1344 | this.request = request; 1345 | var transport = this.transport = request.transport, 1346 | readyState = this.readyState = transport.readyState; 1347 | 1348 | if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { 1349 | this.status = this.getStatus(); 1350 | this.statusText = this.getStatusText(); 1351 | this.responseText = String.interpret(transport.responseText); 1352 | this.headerJSON = this._getHeaderJSON(); 1353 | } 1354 | 1355 | if(readyState == 4) { 1356 | var xml = transport.responseXML; 1357 | this.responseXML = Object.isUndefined(xml) ? null : xml; 1358 | this.responseJSON = this._getResponseJSON(); 1359 | } 1360 | }, 1361 | 1362 | status: 0, 1363 | statusText: '', 1364 | 1365 | getStatus: Ajax.Request.prototype.getStatus, 1366 | 1367 | getStatusText: function() { 1368 | try { 1369 | return this.transport.statusText || ''; 1370 | } catch (e) { return '' } 1371 | }, 1372 | 1373 | getHeader: Ajax.Request.prototype.getHeader, 1374 | 1375 | getAllHeaders: function() { 1376 | try { 1377 | return this.getAllResponseHeaders(); 1378 | } catch (e) { return null } 1379 | }, 1380 | 1381 | getResponseHeader: function(name) { 1382 | return this.transport.getResponseHeader(name); 1383 | }, 1384 | 1385 | getAllResponseHeaders: function() { 1386 | return this.transport.getAllResponseHeaders(); 1387 | }, 1388 | 1389 | _getHeaderJSON: function() { 1390 | var json = this.getHeader('X-JSON'); 1391 | if (!json) return null; 1392 | json = decodeURIComponent(escape(json)); 1393 | try { 1394 | return json.evalJSON(this.request.options.sanitizeJSON); 1395 | } catch (e) { 1396 | this.request.dispatchException(e); 1397 | } 1398 | }, 1399 | 1400 | _getResponseJSON: function() { 1401 | var options = this.request.options; 1402 | if (!options.evalJSON || (options.evalJSON != 'force' && 1403 | !(this.getHeader('Content-type') || '').include('application/json')) || 1404 | this.responseText.blank()) 1405 | return null; 1406 | try { 1407 | return this.responseText.evalJSON(options.sanitizeJSON); 1408 | } catch (e) { 1409 | this.request.dispatchException(e); 1410 | } 1411 | } 1412 | }); 1413 | 1414 | Ajax.Updater = Class.create(Ajax.Request, { 1415 | initialize: function($super, container, url, options) { 1416 | this.container = { 1417 | success: (container.success || container), 1418 | failure: (container.failure || (container.success ? null : container)) 1419 | }; 1420 | 1421 | options = Object.clone(options); 1422 | var onComplete = options.onComplete; 1423 | options.onComplete = (function(response, json) { 1424 | this.updateContent(response.responseText); 1425 | if (Object.isFunction(onComplete)) onComplete(response, json); 1426 | }).bind(this); 1427 | 1428 | $super(url, options); 1429 | }, 1430 | 1431 | updateContent: function(responseText) { 1432 | var receiver = this.container[this.success() ? 'success' : 'failure'], 1433 | options = this.options; 1434 | 1435 | if (!options.evalScripts) responseText = responseText.stripScripts(); 1436 | 1437 | if (receiver = $(receiver)) { 1438 | if (options.insertion) { 1439 | if (Object.isString(options.insertion)) { 1440 | var insertion = { }; insertion[options.insertion] = responseText; 1441 | receiver.insert(insertion); 1442 | } 1443 | else options.insertion(receiver, responseText); 1444 | } 1445 | else receiver.update(responseText); 1446 | } 1447 | } 1448 | }); 1449 | 1450 | Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { 1451 | initialize: function($super, container, url, options) { 1452 | $super(options); 1453 | this.onComplete = this.options.onComplete; 1454 | 1455 | this.frequency = (this.options.frequency || 2); 1456 | this.decay = (this.options.decay || 1); 1457 | 1458 | this.updater = { }; 1459 | this.container = container; 1460 | this.url = url; 1461 | 1462 | this.start(); 1463 | }, 1464 | 1465 | start: function() { 1466 | this.options.onComplete = this.updateComplete.bind(this); 1467 | this.onTimerEvent(); 1468 | }, 1469 | 1470 | stop: function() { 1471 | this.updater.options.onComplete = undefined; 1472 | clearTimeout(this.timer); 1473 | (this.onComplete || Prototype.emptyFunction).apply(this, arguments); 1474 | }, 1475 | 1476 | updateComplete: function(response) { 1477 | if (this.options.decay) { 1478 | this.decay = (response.responseText == this.lastText ? 1479 | this.decay * this.options.decay : 1); 1480 | 1481 | this.lastText = response.responseText; 1482 | } 1483 | this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); 1484 | }, 1485 | 1486 | onTimerEvent: function() { 1487 | this.updater = new Ajax.Updater(this.container, this.url, this.options); 1488 | } 1489 | }); 1490 | function $(element) { 1491 | if (arguments.length > 1) { 1492 | for (var i = 0, elements = [], length = arguments.length; i < length; i++) 1493 | elements.push($(arguments[i])); 1494 | return elements; 1495 | } 1496 | if (Object.isString(element)) 1497 | element = document.getElementById(element); 1498 | return Element.extend(element); 1499 | } 1500 | 1501 | if (Prototype.BrowserFeatures.XPath) { 1502 | document._getElementsByXPath = function(expression, parentElement) { 1503 | var results = []; 1504 | var query = document.evaluate(expression, $(parentElement) || document, 1505 | null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 1506 | for (var i = 0, length = query.snapshotLength; i < length; i++) 1507 | results.push(Element.extend(query.snapshotItem(i))); 1508 | return results; 1509 | }; 1510 | } 1511 | 1512 | /*--------------------------------------------------------------------------*/ 1513 | 1514 | if (!window.Node) var Node = { }; 1515 | 1516 | if (!Node.ELEMENT_NODE) { 1517 | // DOM level 2 ECMAScript Language Binding 1518 | Object.extend(Node, { 1519 | ELEMENT_NODE: 1, 1520 | ATTRIBUTE_NODE: 2, 1521 | TEXT_NODE: 3, 1522 | CDATA_SECTION_NODE: 4, 1523 | ENTITY_REFERENCE_NODE: 5, 1524 | ENTITY_NODE: 6, 1525 | PROCESSING_INSTRUCTION_NODE: 7, 1526 | COMMENT_NODE: 8, 1527 | DOCUMENT_NODE: 9, 1528 | DOCUMENT_TYPE_NODE: 10, 1529 | DOCUMENT_FRAGMENT_NODE: 11, 1530 | NOTATION_NODE: 12 1531 | }); 1532 | } 1533 | 1534 | (function() { 1535 | var element = this.Element; 1536 | this.Element = function(tagName, attributes) { 1537 | attributes = attributes || { }; 1538 | tagName = tagName.toLowerCase(); 1539 | var cache = Element.cache; 1540 | if (Prototype.Browser.IE && attributes.name) { 1541 | tagName = '<' + tagName + ' name="' + attributes.name + '">'; 1542 | delete attributes.name; 1543 | return Element.writeAttribute(document.createElement(tagName), attributes); 1544 | } 1545 | if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); 1546 | return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); 1547 | }; 1548 | Object.extend(this.Element, element || { }); 1549 | }).call(window); 1550 | 1551 | Element.cache = { }; 1552 | 1553 | Element.Methods = { 1554 | visible: function(element) { 1555 | return $(element).style.display != 'none'; 1556 | }, 1557 | 1558 | toggle: function(element) { 1559 | element = $(element); 1560 | Element[Element.visible(element) ? 'hide' : 'show'](element); 1561 | return element; 1562 | }, 1563 | 1564 | hide: function(element) { 1565 | $(element).style.display = 'none'; 1566 | return element; 1567 | }, 1568 | 1569 | show: function(element) { 1570 | $(element).style.display = ''; 1571 | return element; 1572 | }, 1573 | 1574 | remove: function(element) { 1575 | element = $(element); 1576 | element.parentNode.removeChild(element); 1577 | return element; 1578 | }, 1579 | 1580 | update: function(element, content) { 1581 | element = $(element); 1582 | if (content && content.toElement) content = content.toElement(); 1583 | if (Object.isElement(content)) return element.update().insert(content); 1584 | content = Object.toHTML(content); 1585 | element.innerHTML = content.stripScripts(); 1586 | content.evalScripts.bind(content).defer(); 1587 | return element; 1588 | }, 1589 | 1590 | replace: function(element, content) { 1591 | element = $(element); 1592 | if (content && content.toElement) content = content.toElement(); 1593 | else if (!Object.isElement(content)) { 1594 | content = Object.toHTML(content); 1595 | var range = element.ownerDocument.createRange(); 1596 | range.selectNode(element); 1597 | content.evalScripts.bind(content).defer(); 1598 | content = range.createContextualFragment(content.stripScripts()); 1599 | } 1600 | element.parentNode.replaceChild(content, element); 1601 | return element; 1602 | }, 1603 | 1604 | insert: function(element, insertions) { 1605 | element = $(element); 1606 | 1607 | if (Object.isString(insertions) || Object.isNumber(insertions) || 1608 | Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) 1609 | insertions = {bottom:insertions}; 1610 | 1611 | var content, t, range; 1612 | 1613 | for (position in insertions) { 1614 | content = insertions[position]; 1615 | position = position.toLowerCase(); 1616 | t = Element._insertionTranslations[position]; 1617 | 1618 | if (content && content.toElement) content = content.toElement(); 1619 | if (Object.isElement(content)) { 1620 | t.insert(element, content); 1621 | continue; 1622 | } 1623 | 1624 | content = Object.toHTML(content); 1625 | 1626 | range = element.ownerDocument.createRange(); 1627 | t.initializeRange(element, range); 1628 | t.insert(element, range.createContextualFragment(content.stripScripts())); 1629 | 1630 | content.evalScripts.bind(content).defer(); 1631 | } 1632 | 1633 | return element; 1634 | }, 1635 | 1636 | wrap: function(element, wrapper, attributes) { 1637 | element = $(element); 1638 | if (Object.isElement(wrapper)) 1639 | $(wrapper).writeAttribute(attributes || { }); 1640 | else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); 1641 | else wrapper = new Element('div', wrapper); 1642 | if (element.parentNode) 1643 | element.parentNode.replaceChild(wrapper, element); 1644 | wrapper.appendChild(element); 1645 | return wrapper; 1646 | }, 1647 | 1648 | inspect: function(element) { 1649 | element = $(element); 1650 | var result = '<' + element.tagName.toLowerCase(); 1651 | $H({'id': 'id', 'className': 'class'}).each(function(pair) { 1652 | var property = pair.first(), attribute = pair.last(); 1653 | var value = (element[property] || '').toString(); 1654 | if (value) result += ' ' + attribute + '=' + value.inspect(true); 1655 | }); 1656 | return result + '>'; 1657 | }, 1658 | 1659 | recursivelyCollect: function(element, property) { 1660 | element = $(element); 1661 | var elements = []; 1662 | while (element = element[property]) 1663 | if (element.nodeType == 1) 1664 | elements.push(Element.extend(element)); 1665 | return elements; 1666 | }, 1667 | 1668 | ancestors: function(element) { 1669 | return $(element).recursivelyCollect('parentNode'); 1670 | }, 1671 | 1672 | descendants: function(element) { 1673 | return $(element).getElementsBySelector("*"); 1674 | }, 1675 | 1676 | firstDescendant: function(element) { 1677 | element = $(element).firstChild; 1678 | while (element && element.nodeType != 1) element = element.nextSibling; 1679 | return $(element); 1680 | }, 1681 | 1682 | immediateDescendants: function(element) { 1683 | if (!(element = $(element).firstChild)) return []; 1684 | while (element && element.nodeType != 1) element = element.nextSibling; 1685 | if (element) return [element].concat($(element).nextSiblings()); 1686 | return []; 1687 | }, 1688 | 1689 | previousSiblings: function(element) { 1690 | return $(element).recursivelyCollect('previousSibling'); 1691 | }, 1692 | 1693 | nextSiblings: function(element) { 1694 | return $(element).recursivelyCollect('nextSibling'); 1695 | }, 1696 | 1697 | siblings: function(element) { 1698 | element = $(element); 1699 | return element.previousSiblings().reverse().concat(element.nextSiblings()); 1700 | }, 1701 | 1702 | match: function(element, selector) { 1703 | if (Object.isString(selector)) 1704 | selector = new Selector(selector); 1705 | return selector.match($(element)); 1706 | }, 1707 | 1708 | up: function(element, expression, index) { 1709 | element = $(element); 1710 | if (arguments.length == 1) return $(element.parentNode); 1711 | var ancestors = element.ancestors(); 1712 | return expression ? Selector.findElement(ancestors, expression, index) : 1713 | ancestors[index || 0]; 1714 | }, 1715 | 1716 | down: function(element, expression, index) { 1717 | element = $(element); 1718 | if (arguments.length == 1) return element.firstDescendant(); 1719 | var descendants = element.descendants(); 1720 | return expression ? Selector.findElement(descendants, expression, index) : 1721 | descendants[index || 0]; 1722 | }, 1723 | 1724 | previous: function(element, expression, index) { 1725 | element = $(element); 1726 | if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); 1727 | var previousSiblings = element.previousSiblings(); 1728 | return expression ? Selector.findElement(previousSiblings, expression, index) : 1729 | previousSiblings[index || 0]; 1730 | }, 1731 | 1732 | next: function(element, expression, index) { 1733 | element = $(element); 1734 | if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); 1735 | var nextSiblings = element.nextSiblings(); 1736 | return expression ? Selector.findElement(nextSiblings, expression, index) : 1737 | nextSiblings[index || 0]; 1738 | }, 1739 | 1740 | select: function() { 1741 | var args = $A(arguments), element = $(args.shift()); 1742 | return Selector.findChildElements(element, args); 1743 | }, 1744 | 1745 | adjacent: function() { 1746 | var args = $A(arguments), element = $(args.shift()); 1747 | return Selector.findChildElements(element.parentNode, args).without(element); 1748 | }, 1749 | 1750 | identify: function(element) { 1751 | element = $(element); 1752 | var id = element.readAttribute('id'), self = arguments.callee; 1753 | if (id) return id; 1754 | do { id = 'anonymous_element_' + self.counter++ } while ($(id)); 1755 | element.writeAttribute('id', id); 1756 | return id; 1757 | }, 1758 | 1759 | readAttribute: function(element, name) { 1760 | element = $(element); 1761 | if (Prototype.Browser.IE) { 1762 | var t = Element._attributeTranslations.read; 1763 | if (t.values[name]) return t.values[name](element, name); 1764 | if (t.names[name]) name = t.names[name]; 1765 | if (name.include(':')) { 1766 | return (!element.attributes || !element.attributes[name]) ? null : 1767 | element.attributes[name].value; 1768 | } 1769 | } 1770 | return element.getAttribute(name); 1771 | }, 1772 | 1773 | writeAttribute: function(element, name, value) { 1774 | element = $(element); 1775 | var attributes = { }, t = Element._attributeTranslations.write; 1776 | 1777 | if (typeof name == 'object') attributes = name; 1778 | else attributes[name] = Object.isUndefined(value) ? true : value; 1779 | 1780 | for (var attr in attributes) { 1781 | name = t.names[attr] || attr; 1782 | value = attributes[attr]; 1783 | if (t.values[attr]) name = t.values[attr](element, value); 1784 | if (value === false || value === null) 1785 | element.removeAttribute(name); 1786 | else if (value === true) 1787 | element.setAttribute(name, name); 1788 | else element.setAttribute(name, value); 1789 | } 1790 | return element; 1791 | }, 1792 | 1793 | getHeight: function(element) { 1794 | return $(element).getDimensions().height; 1795 | }, 1796 | 1797 | getWidth: function(element) { 1798 | return $(element).getDimensions().width; 1799 | }, 1800 | 1801 | classNames: function(element) { 1802 | return new Element.ClassNames(element); 1803 | }, 1804 | 1805 | hasClassName: function(element, className) { 1806 | if (!(element = $(element))) return; 1807 | var elementClassName = element.className; 1808 | return (elementClassName.length > 0 && (elementClassName == className || 1809 | new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); 1810 | }, 1811 | 1812 | addClassName: function(element, className) { 1813 | if (!(element = $(element))) return; 1814 | if (!element.hasClassName(className)) 1815 | element.className += (element.className ? ' ' : '') + className; 1816 | return element; 1817 | }, 1818 | 1819 | removeClassName: function(element, className) { 1820 | if (!(element = $(element))) return; 1821 | element.className = element.className.replace( 1822 | new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); 1823 | return element; 1824 | }, 1825 | 1826 | toggleClassName: function(element, className) { 1827 | if (!(element = $(element))) return; 1828 | return element[element.hasClassName(className) ? 1829 | 'removeClassName' : 'addClassName'](className); 1830 | }, 1831 | 1832 | // removes whitespace-only text node children 1833 | cleanWhitespace: function(element) { 1834 | element = $(element); 1835 | var node = element.firstChild; 1836 | while (node) { 1837 | var nextNode = node.nextSibling; 1838 | if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) 1839 | element.removeChild(node); 1840 | node = nextNode; 1841 | } 1842 | return element; 1843 | }, 1844 | 1845 | empty: function(element) { 1846 | return $(element).innerHTML.blank(); 1847 | }, 1848 | 1849 | descendantOf: function(element, ancestor) { 1850 | element = $(element), ancestor = $(ancestor); 1851 | var originalAncestor = ancestor; 1852 | 1853 | if (element.compareDocumentPosition) 1854 | return (element.compareDocumentPosition(ancestor) & 8) === 8; 1855 | 1856 | if (element.sourceIndex && !Prototype.Browser.Opera) { 1857 | var e = element.sourceIndex, a = ancestor.sourceIndex, 1858 | nextAncestor = ancestor.nextSibling; 1859 | if (!nextAncestor) { 1860 | do { ancestor = ancestor.parentNode; } 1861 | while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); 1862 | } 1863 | if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); 1864 | } 1865 | 1866 | while (element = element.parentNode) 1867 | if (element == originalAncestor) return true; 1868 | return false; 1869 | }, 1870 | 1871 | scrollTo: function(element) { 1872 | element = $(element); 1873 | var pos = element.cumulativeOffset(); 1874 | window.scrollTo(pos[0], pos[1]); 1875 | return element; 1876 | }, 1877 | 1878 | getStyle: function(element, style) { 1879 | element = $(element); 1880 | style = style == 'float' ? 'cssFloat' : style.camelize(); 1881 | var value = element.style[style]; 1882 | if (!value) { 1883 | var css = document.defaultView.getComputedStyle(element, null); 1884 | value = css ? css[style] : null; 1885 | } 1886 | if (style == 'opacity') return value ? parseFloat(value) : 1.0; 1887 | return value == 'auto' ? null : value; 1888 | }, 1889 | 1890 | getOpacity: function(element) { 1891 | return $(element).getStyle('opacity'); 1892 | }, 1893 | 1894 | setStyle: function(element, styles) { 1895 | element = $(element); 1896 | var elementStyle = element.style, match; 1897 | if (Object.isString(styles)) { 1898 | element.style.cssText += ';' + styles; 1899 | return styles.include('opacity') ? 1900 | element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; 1901 | } 1902 | for (var property in styles) 1903 | if (property == 'opacity') element.setOpacity(styles[property]); 1904 | else 1905 | elementStyle[(property == 'float' || property == 'cssFloat') ? 1906 | (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : 1907 | property] = styles[property]; 1908 | 1909 | return element; 1910 | }, 1911 | 1912 | setOpacity: function(element, value) { 1913 | element = $(element); 1914 | element.style.opacity = (value == 1 || value === '') ? '' : 1915 | (value < 0.00001) ? 0 : value; 1916 | return element; 1917 | }, 1918 | 1919 | getDimensions: function(element) { 1920 | element = $(element); 1921 | var display = $(element).getStyle('display'); 1922 | if (display != 'none' && display != null) // Safari bug 1923 | return {width: element.offsetWidth, height: element.offsetHeight}; 1924 | 1925 | // All *Width and *Height properties give 0 on elements with display none, 1926 | // so enable the element temporarily 1927 | var els = element.style; 1928 | var originalVisibility = els.visibility; 1929 | var originalPosition = els.position; 1930 | var originalDisplay = els.display; 1931 | els.visibility = 'hidden'; 1932 | els.position = 'absolute'; 1933 | els.display = 'block'; 1934 | var originalWidth = element.clientWidth; 1935 | var originalHeight = element.clientHeight; 1936 | els.display = originalDisplay; 1937 | els.position = originalPosition; 1938 | els.visibility = originalVisibility; 1939 | return {width: originalWidth, height: originalHeight}; 1940 | }, 1941 | 1942 | makePositioned: function(element) { 1943 | element = $(element); 1944 | var pos = Element.getStyle(element, 'position'); 1945 | if (pos == 'static' || !pos) { 1946 | element._madePositioned = true; 1947 | element.style.position = 'relative'; 1948 | // Opera returns the offset relative to the positioning context, when an 1949 | // element is position relative but top and left have not been defined 1950 | if (window.opera) { 1951 | element.style.top = 0; 1952 | element.style.left = 0; 1953 | } 1954 | } 1955 | return element; 1956 | }, 1957 | 1958 | undoPositioned: function(element) { 1959 | element = $(element); 1960 | if (element._madePositioned) { 1961 | element._madePositioned = undefined; 1962 | element.style.position = 1963 | element.style.top = 1964 | element.style.left = 1965 | element.style.bottom = 1966 | element.style.right = ''; 1967 | } 1968 | return element; 1969 | }, 1970 | 1971 | makeClipping: function(element) { 1972 | element = $(element); 1973 | if (element._overflow) return element; 1974 | element._overflow = Element.getStyle(element, 'overflow') || 'auto'; 1975 | if (element._overflow !== 'hidden') 1976 | element.style.overflow = 'hidden'; 1977 | return element; 1978 | }, 1979 | 1980 | undoClipping: function(element) { 1981 | element = $(element); 1982 | if (!element._overflow) return element; 1983 | element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; 1984 | element._overflow = null; 1985 | return element; 1986 | }, 1987 | 1988 | cumulativeOffset: function(element) { 1989 | var valueT = 0, valueL = 0; 1990 | do { 1991 | valueT += element.offsetTop || 0; 1992 | valueL += element.offsetLeft || 0; 1993 | element = element.offsetParent; 1994 | } while (element); 1995 | return Element._returnOffset(valueL, valueT); 1996 | }, 1997 | 1998 | positionedOffset: function(element) { 1999 | var valueT = 0, valueL = 0; 2000 | do { 2001 | valueT += element.offsetTop || 0; 2002 | valueL += element.offsetLeft || 0; 2003 | element = element.offsetParent; 2004 | if (element) { 2005 | if (element.tagName == 'BODY') break; 2006 | var p = Element.getStyle(element, 'position'); 2007 | if (p == 'relative' || p == 'absolute') break; 2008 | } 2009 | } while (element); 2010 | return Element._returnOffset(valueL, valueT); 2011 | }, 2012 | 2013 | absolutize: function(element) { 2014 | element = $(element); 2015 | if (element.getStyle('position') == 'absolute') return; 2016 | // Position.prepare(); // To be done manually by Scripty when it needs it. 2017 | 2018 | var offsets = element.positionedOffset(); 2019 | var top = offsets[1]; 2020 | var left = offsets[0]; 2021 | var width = element.clientWidth; 2022 | var height = element.clientHeight; 2023 | 2024 | element._originalLeft = left - parseFloat(element.style.left || 0); 2025 | element._originalTop = top - parseFloat(element.style.top || 0); 2026 | element._originalWidth = element.style.width; 2027 | element._originalHeight = element.style.height; 2028 | 2029 | element.style.position = 'absolute'; 2030 | element.style.top = top + 'px'; 2031 | element.style.left = left + 'px'; 2032 | element.style.width = width + 'px'; 2033 | element.style.height = height + 'px'; 2034 | return element; 2035 | }, 2036 | 2037 | relativize: function(element) { 2038 | element = $(element); 2039 | if (element.getStyle('position') == 'relative') return; 2040 | // Position.prepare(); // To be done manually by Scripty when it needs it. 2041 | 2042 | element.style.position = 'relative'; 2043 | var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); 2044 | var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); 2045 | 2046 | element.style.top = top + 'px'; 2047 | element.style.left = left + 'px'; 2048 | element.style.height = element._originalHeight; 2049 | element.style.width = element._originalWidth; 2050 | return element; 2051 | }, 2052 | 2053 | cumulativeScrollOffset: function(element) { 2054 | var valueT = 0, valueL = 0; 2055 | do { 2056 | valueT += element.scrollTop || 0; 2057 | valueL += element.scrollLeft || 0; 2058 | element = element.parentNode; 2059 | } while (element); 2060 | return Element._returnOffset(valueL, valueT); 2061 | }, 2062 | 2063 | getOffsetParent: function(element) { 2064 | if (element.offsetParent) return $(element.offsetParent); 2065 | if (element == document.body) return $(element); 2066 | 2067 | while ((element = element.parentNode) && element != document.body) 2068 | if (Element.getStyle(element, 'position') != 'static') 2069 | return $(element); 2070 | 2071 | return $(document.body); 2072 | }, 2073 | 2074 | viewportOffset: function(forElement) { 2075 | var valueT = 0, valueL = 0; 2076 | 2077 | var element = forElement; 2078 | do { 2079 | valueT += element.offsetTop || 0; 2080 | valueL += element.offsetLeft || 0; 2081 | 2082 | // Safari fix 2083 | if (element.offsetParent == document.body && 2084 | Element.getStyle(element, 'position') == 'absolute') break; 2085 | 2086 | } while (element = element.offsetParent); 2087 | 2088 | element = forElement; 2089 | do { 2090 | if (!Prototype.Browser.Opera || element.tagName == 'BODY') { 2091 | valueT -= element.scrollTop || 0; 2092 | valueL -= element.scrollLeft || 0; 2093 | } 2094 | } while (element = element.parentNode); 2095 | 2096 | return Element._returnOffset(valueL, valueT); 2097 | }, 2098 | 2099 | clonePosition: function(element, source) { 2100 | var options = Object.extend({ 2101 | setLeft: true, 2102 | setTop: true, 2103 | setWidth: true, 2104 | setHeight: true, 2105 | offsetTop: 0, 2106 | offsetLeft: 0 2107 | }, arguments[2] || { }); 2108 | 2109 | // find page position of source 2110 | source = $(source); 2111 | var p = source.viewportOffset(); 2112 | 2113 | // find coordinate system to use 2114 | element = $(element); 2115 | var delta = [0, 0]; 2116 | var parent = null; 2117 | // delta [0,0] will do fine with position: fixed elements, 2118 | // position:absolute needs offsetParent deltas 2119 | if (Element.getStyle(element, 'position') == 'absolute') { 2120 | parent = element.getOffsetParent(); 2121 | delta = parent.viewportOffset(); 2122 | } 2123 | 2124 | // correct by body offsets (fixes Safari) 2125 | if (parent == document.body) { 2126 | delta[0] -= document.body.offsetLeft; 2127 | delta[1] -= document.body.offsetTop; 2128 | } 2129 | 2130 | // set position 2131 | if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; 2132 | if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; 2133 | if (options.setWidth) element.style.width = source.offsetWidth + 'px'; 2134 | if (options.setHeight) element.style.height = source.offsetHeight + 'px'; 2135 | return element; 2136 | } 2137 | }; 2138 | 2139 | Element.Methods.identify.counter = 1; 2140 | 2141 | Object.extend(Element.Methods, { 2142 | getElementsBySelector: Element.Methods.select, 2143 | childElements: Element.Methods.immediateDescendants 2144 | }); 2145 | 2146 | Element._attributeTranslations = { 2147 | write: { 2148 | names: { 2149 | className: 'class', 2150 | htmlFor: 'for' 2151 | }, 2152 | values: { } 2153 | } 2154 | }; 2155 | 2156 | 2157 | if (!document.createRange || Prototype.Browser.Opera) { 2158 | Element.Methods.insert = function(element, insertions) { 2159 | element = $(element); 2160 | 2161 | if (Object.isString(insertions) || Object.isNumber(insertions) || 2162 | Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) 2163 | insertions = { bottom: insertions }; 2164 | 2165 | var t = Element._insertionTranslations, content, position, pos, tagName; 2166 | 2167 | for (position in insertions) { 2168 | content = insertions[position]; 2169 | position = position.toLowerCase(); 2170 | pos = t[position]; 2171 | 2172 | if (content && content.toElement) content = content.toElement(); 2173 | if (Object.isElement(content)) { 2174 | pos.insert(element, content); 2175 | continue; 2176 | } 2177 | 2178 | content = Object.toHTML(content); 2179 | tagName = ((position == 'before' || position == 'after') 2180 | ? element.parentNode : element).tagName.toUpperCase(); 2181 | 2182 | if (t.tags[tagName]) { 2183 | var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); 2184 | if (position == 'top' || position == 'after') fragments.reverse(); 2185 | fragments.each(pos.insert.curry(element)); 2186 | } 2187 | else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); 2188 | 2189 | content.evalScripts.bind(content).defer(); 2190 | } 2191 | 2192 | return element; 2193 | }; 2194 | } 2195 | 2196 | if (Prototype.Browser.Opera) { 2197 | Element.Methods.getStyle = Element.Methods.getStyle.wrap( 2198 | function(proceed, element, style) { 2199 | switch (style) { 2200 | case 'left': case 'top': case 'right': case 'bottom': 2201 | if (proceed(element, 'position') === 'static') return null; 2202 | case 'height': case 'width': 2203 | // returns '0px' for hidden elements; we want it to return null 2204 | if (!Element.visible(element)) return null; 2205 | 2206 | // returns the border-box dimensions rather than the content-box 2207 | // dimensions, so we subtract padding and borders from the value 2208 | var dim = parseInt(proceed(element, style), 10); 2209 | 2210 | if (dim !== element['offset' + style.capitalize()]) 2211 | return dim + 'px'; 2212 | 2213 | var properties; 2214 | if (style === 'height') { 2215 | properties = ['border-top-width', 'padding-top', 2216 | 'padding-bottom', 'border-bottom-width']; 2217 | } 2218 | else { 2219 | properties = ['border-left-width', 'padding-left', 2220 | 'padding-right', 'border-right-width']; 2221 | } 2222 | return properties.inject(dim, function(memo, property) { 2223 | var val = proceed(element, property); 2224 | return val === null ? memo : memo - parseInt(val, 10); 2225 | }) + 'px'; 2226 | default: return proceed(element, style); 2227 | } 2228 | } 2229 | ); 2230 | 2231 | Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( 2232 | function(proceed, element, attribute) { 2233 | if (attribute === 'title') return element.title; 2234 | return proceed(element, attribute); 2235 | } 2236 | ); 2237 | } 2238 | 2239 | else if (Prototype.Browser.IE) { 2240 | $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { 2241 | Element.Methods[method] = Element.Methods[method].wrap( 2242 | function(proceed, element) { 2243 | element = $(element); 2244 | var position = element.getStyle('position'); 2245 | if (position != 'static') return proceed(element); 2246 | element.setStyle({ position: 'relative' }); 2247 | var value = proceed(element); 2248 | element.setStyle({ position: position }); 2249 | return value; 2250 | } 2251 | ); 2252 | }); 2253 | 2254 | Element.Methods.getStyle = function(element, style) { 2255 | element = $(element); 2256 | style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); 2257 | var value = element.style[style]; 2258 | if (!value && element.currentStyle) value = element.currentStyle[style]; 2259 | 2260 | if (style == 'opacity') { 2261 | if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) 2262 | if (value[1]) return parseFloat(value[1]) / 100; 2263 | return 1.0; 2264 | } 2265 | 2266 | if (value == 'auto') { 2267 | if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) 2268 | return element['offset' + style.capitalize()] + 'px'; 2269 | return null; 2270 | } 2271 | return value; 2272 | }; 2273 | 2274 | Element.Methods.setOpacity = function(element, value) { 2275 | function stripAlpha(filter){ 2276 | return filter.replace(/alpha\([^\)]*\)/gi,''); 2277 | } 2278 | element = $(element); 2279 | var currentStyle = element.currentStyle; 2280 | if ((currentStyle && !currentStyle.hasLayout) || 2281 | (!currentStyle && element.style.zoom == 'normal')) 2282 | element.style.zoom = 1; 2283 | 2284 | var filter = element.getStyle('filter'), style = element.style; 2285 | if (value == 1 || value === '') { 2286 | (filter = stripAlpha(filter)) ? 2287 | style.filter = filter : style.removeAttribute('filter'); 2288 | return element; 2289 | } else if (value < 0.00001) value = 0; 2290 | style.filter = stripAlpha(filter) + 2291 | 'alpha(opacity=' + (value * 100) + ')'; 2292 | return element; 2293 | }; 2294 | 2295 | Element._attributeTranslations = { 2296 | read: { 2297 | names: { 2298 | 'class': 'className', 2299 | 'for': 'htmlFor' 2300 | }, 2301 | values: { 2302 | _getAttr: function(element, attribute) { 2303 | return element.getAttribute(attribute, 2); 2304 | }, 2305 | _getAttrNode: function(element, attribute) { 2306 | var node = element.getAttributeNode(attribute); 2307 | return node ? node.value : ""; 2308 | }, 2309 | _getEv: function(element, attribute) { 2310 | attribute = element.getAttribute(attribute); 2311 | return attribute ? attribute.toString().slice(23, -2) : null; 2312 | }, 2313 | _flag: function(element, attribute) { 2314 | return $(element).hasAttribute(attribute) ? attribute : null; 2315 | }, 2316 | style: function(element) { 2317 | return element.style.cssText.toLowerCase(); 2318 | }, 2319 | title: function(element) { 2320 | return element.title; 2321 | } 2322 | } 2323 | } 2324 | }; 2325 | 2326 | Element._attributeTranslations.write = { 2327 | names: Object.clone(Element._attributeTranslations.read.names), 2328 | values: { 2329 | checked: function(element, value) { 2330 | element.checked = !!value; 2331 | }, 2332 | 2333 | style: function(element, value) { 2334 | element.style.cssText = value ? value : ''; 2335 | } 2336 | } 2337 | }; 2338 | 2339 | Element._attributeTranslations.has = {}; 2340 | 2341 | $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 2342 | 'encType maxLength readOnly longDesc').each(function(attr) { 2343 | Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; 2344 | Element._attributeTranslations.has[attr.toLowerCase()] = attr; 2345 | }); 2346 | 2347 | (function(v) { 2348 | Object.extend(v, { 2349 | href: v._getAttr, 2350 | src: v._getAttr, 2351 | type: v._getAttr, 2352 | action: v._getAttrNode, 2353 | disabled: v._flag, 2354 | checked: v._flag, 2355 | readonly: v._flag, 2356 | multiple: v._flag, 2357 | onload: v._getEv, 2358 | onunload: v._getEv, 2359 | onclick: v._getEv, 2360 | ondblclick: v._getEv, 2361 | onmousedown: v._getEv, 2362 | onmouseup: v._getEv, 2363 | onmouseover: v._getEv, 2364 | onmousemove: v._getEv, 2365 | onmouseout: v._getEv, 2366 | onfocus: v._getEv, 2367 | onblur: v._getEv, 2368 | onkeypress: v._getEv, 2369 | onkeydown: v._getEv, 2370 | onkeyup: v._getEv, 2371 | onsubmit: v._getEv, 2372 | onreset: v._getEv, 2373 | onselect: v._getEv, 2374 | onchange: v._getEv 2375 | }); 2376 | })(Element._attributeTranslations.read.values); 2377 | } 2378 | 2379 | else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { 2380 | Element.Methods.setOpacity = function(element, value) { 2381 | element = $(element); 2382 | element.style.opacity = (value == 1) ? 0.999999 : 2383 | (value === '') ? '' : (value < 0.00001) ? 0 : value; 2384 | return element; 2385 | }; 2386 | } 2387 | 2388 | else if (Prototype.Browser.WebKit) { 2389 | Element.Methods.setOpacity = function(element, value) { 2390 | element = $(element); 2391 | element.style.opacity = (value == 1 || value === '') ? '' : 2392 | (value < 0.00001) ? 0 : value; 2393 | 2394 | if (value == 1) 2395 | if(element.tagName == 'IMG' && element.width) { 2396 | element.width++; element.width--; 2397 | } else try { 2398 | var n = document.createTextNode(' '); 2399 | element.appendChild(n); 2400 | element.removeChild(n); 2401 | } catch (e) { } 2402 | 2403 | return element; 2404 | }; 2405 | 2406 | // Safari returns margins on body which is incorrect if the child is absolutely 2407 | // positioned. For performance reasons, redefine Element#cumulativeOffset for 2408 | // KHTML/WebKit only. 2409 | Element.Methods.cumulativeOffset = function(element) { 2410 | var valueT = 0, valueL = 0; 2411 | do { 2412 | valueT += element.offsetTop || 0; 2413 | valueL += element.offsetLeft || 0; 2414 | if (element.offsetParent == document.body) 2415 | if (Element.getStyle(element, 'position') == 'absolute') break; 2416 | 2417 | element = element.offsetParent; 2418 | } while (element); 2419 | 2420 | return Element._returnOffset(valueL, valueT); 2421 | }; 2422 | } 2423 | 2424 | if (Prototype.Browser.IE || Prototype.Browser.Opera) { 2425 | // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements 2426 | Element.Methods.update = function(element, content) { 2427 | element = $(element); 2428 | 2429 | if (content && content.toElement) content = content.toElement(); 2430 | if (Object.isElement(content)) return element.update().insert(content); 2431 | 2432 | content = Object.toHTML(content); 2433 | var tagName = element.tagName.toUpperCase(); 2434 | 2435 | if (tagName in Element._insertionTranslations.tags) { 2436 | $A(element.childNodes).each(function(node) { element.removeChild(node) }); 2437 | Element._getContentFromAnonymousElement(tagName, content.stripScripts()) 2438 | .each(function(node) { element.appendChild(node) }); 2439 | } 2440 | else element.innerHTML = content.stripScripts(); 2441 | 2442 | content.evalScripts.bind(content).defer(); 2443 | return element; 2444 | }; 2445 | } 2446 | 2447 | if (document.createElement('div').outerHTML) { 2448 | Element.Methods.replace = function(element, content) { 2449 | element = $(element); 2450 | 2451 | if (content && content.toElement) content = content.toElement(); 2452 | if (Object.isElement(content)) { 2453 | element.parentNode.replaceChild(content, element); 2454 | return element; 2455 | } 2456 | 2457 | content = Object.toHTML(content); 2458 | var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); 2459 | 2460 | if (Element._insertionTranslations.tags[tagName]) { 2461 | var nextSibling = element.next(); 2462 | var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); 2463 | parent.removeChild(element); 2464 | if (nextSibling) 2465 | fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); 2466 | else 2467 | fragments.each(function(node) { parent.appendChild(node) }); 2468 | } 2469 | else element.outerHTML = content.stripScripts(); 2470 | 2471 | content.evalScripts.bind(content).defer(); 2472 | return element; 2473 | }; 2474 | } 2475 | 2476 | Element._returnOffset = function(l, t) { 2477 | var result = [l, t]; 2478 | result.left = l; 2479 | result.top = t; 2480 | return result; 2481 | }; 2482 | 2483 | Element._getContentFromAnonymousElement = function(tagName, html) { 2484 | var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; 2485 | div.innerHTML = t[0] + html + t[1]; 2486 | t[2].times(function() { div = div.firstChild }); 2487 | return $A(div.childNodes); 2488 | }; 2489 | 2490 | Element._insertionTranslations = { 2491 | before: { 2492 | adjacency: 'beforeBegin', 2493 | insert: function(element, node) { 2494 | element.parentNode.insertBefore(node, element); 2495 | }, 2496 | initializeRange: function(element, range) { 2497 | range.setStartBefore(element); 2498 | } 2499 | }, 2500 | top: { 2501 | adjacency: 'afterBegin', 2502 | insert: function(element, node) { 2503 | element.insertBefore(node, element.firstChild); 2504 | }, 2505 | initializeRange: function(element, range) { 2506 | range.selectNodeContents(element); 2507 | range.collapse(true); 2508 | } 2509 | }, 2510 | bottom: { 2511 | adjacency: 'beforeEnd', 2512 | insert: function(element, node) { 2513 | element.appendChild(node); 2514 | } 2515 | }, 2516 | after: { 2517 | adjacency: 'afterEnd', 2518 | insert: function(element, node) { 2519 | element.parentNode.insertBefore(node, element.nextSibling); 2520 | }, 2521 | initializeRange: function(element, range) { 2522 | range.setStartAfter(element); 2523 | } 2524 | }, 2525 | tags: { 2526 | TABLE: ['', '
', 1], 2527 | TBODY: ['', '
', 2], 2528 | TR: ['', '
', 3], 2529 | TD: ['
', '
', 4], 2530 | SELECT: ['', 1] 2531 | } 2532 | }; 2533 | 2534 | (function() { 2535 | this.bottom.initializeRange = this.top.initializeRange; 2536 | Object.extend(this.tags, { 2537 | THEAD: this.tags.TBODY, 2538 | TFOOT: this.tags.TBODY, 2539 | TH: this.tags.TD 2540 | }); 2541 | }).call(Element._insertionTranslations); 2542 | 2543 | Element.Methods.Simulated = { 2544 | hasAttribute: function(element, attribute) { 2545 | attribute = Element._attributeTranslations.has[attribute] || attribute; 2546 | var node = $(element).getAttributeNode(attribute); 2547 | return node && node.specified; 2548 | } 2549 | }; 2550 | 2551 | Element.Methods.ByTag = { }; 2552 | 2553 | Object.extend(Element, Element.Methods); 2554 | 2555 | if (!Prototype.BrowserFeatures.ElementExtensions && 2556 | document.createElement('div').__proto__) { 2557 | window.HTMLElement = { }; 2558 | window.HTMLElement.prototype = document.createElement('div').__proto__; 2559 | Prototype.BrowserFeatures.ElementExtensions = true; 2560 | } 2561 | 2562 | Element.extend = (function() { 2563 | if (Prototype.BrowserFeatures.SpecificElementExtensions) 2564 | return Prototype.K; 2565 | 2566 | var Methods = { }, ByTag = Element.Methods.ByTag; 2567 | 2568 | var extend = Object.extend(function(element) { 2569 | if (!element || element._extendedByPrototype || 2570 | element.nodeType != 1 || element == window) return element; 2571 | 2572 | var methods = Object.clone(Methods), 2573 | tagName = element.tagName, property, value; 2574 | 2575 | // extend methods for specific tags 2576 | if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); 2577 | 2578 | for (property in methods) { 2579 | value = methods[property]; 2580 | if (Object.isFunction(value) && !(property in element)) 2581 | element[property] = value.methodize(); 2582 | } 2583 | 2584 | element._extendedByPrototype = Prototype.emptyFunction; 2585 | return element; 2586 | 2587 | }, { 2588 | refresh: function() { 2589 | // extend methods for all tags (Safari doesn't need this) 2590 | if (!Prototype.BrowserFeatures.ElementExtensions) { 2591 | Object.extend(Methods, Element.Methods); 2592 | Object.extend(Methods, Element.Methods.Simulated); 2593 | } 2594 | } 2595 | }); 2596 | 2597 | extend.refresh(); 2598 | return extend; 2599 | })(); 2600 | 2601 | Element.hasAttribute = function(element, attribute) { 2602 | if (element.hasAttribute) return element.hasAttribute(attribute); 2603 | return Element.Methods.Simulated.hasAttribute(element, attribute); 2604 | }; 2605 | 2606 | Element.addMethods = function(methods) { 2607 | var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; 2608 | 2609 | if (!methods) { 2610 | Object.extend(Form, Form.Methods); 2611 | Object.extend(Form.Element, Form.Element.Methods); 2612 | Object.extend(Element.Methods.ByTag, { 2613 | "FORM": Object.clone(Form.Methods), 2614 | "INPUT": Object.clone(Form.Element.Methods), 2615 | "SELECT": Object.clone(Form.Element.Methods), 2616 | "TEXTAREA": Object.clone(Form.Element.Methods) 2617 | }); 2618 | } 2619 | 2620 | if (arguments.length == 2) { 2621 | var tagName = methods; 2622 | methods = arguments[1]; 2623 | } 2624 | 2625 | if (!tagName) Object.extend(Element.Methods, methods || { }); 2626 | else { 2627 | if (Object.isArray(tagName)) tagName.each(extend); 2628 | else extend(tagName); 2629 | } 2630 | 2631 | function extend(tagName) { 2632 | tagName = tagName.toUpperCase(); 2633 | if (!Element.Methods.ByTag[tagName]) 2634 | Element.Methods.ByTag[tagName] = { }; 2635 | Object.extend(Element.Methods.ByTag[tagName], methods); 2636 | } 2637 | 2638 | function copy(methods, destination, onlyIfAbsent) { 2639 | onlyIfAbsent = onlyIfAbsent || false; 2640 | for (var property in methods) { 2641 | var value = methods[property]; 2642 | if (!Object.isFunction(value)) continue; 2643 | if (!onlyIfAbsent || !(property in destination)) 2644 | destination[property] = value.methodize(); 2645 | } 2646 | } 2647 | 2648 | function findDOMClass(tagName) { 2649 | var klass; 2650 | var trans = { 2651 | "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", 2652 | "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", 2653 | "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", 2654 | "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", 2655 | "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": 2656 | "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": 2657 | "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": 2658 | "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": 2659 | "FrameSet", "IFRAME": "IFrame" 2660 | }; 2661 | if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; 2662 | if (window[klass]) return window[klass]; 2663 | klass = 'HTML' + tagName + 'Element'; 2664 | if (window[klass]) return window[klass]; 2665 | klass = 'HTML' + tagName.capitalize() + 'Element'; 2666 | if (window[klass]) return window[klass]; 2667 | 2668 | window[klass] = { }; 2669 | window[klass].prototype = document.createElement(tagName).__proto__; 2670 | return window[klass]; 2671 | } 2672 | 2673 | if (F.ElementExtensions) { 2674 | copy(Element.Methods, HTMLElement.prototype); 2675 | copy(Element.Methods.Simulated, HTMLElement.prototype, true); 2676 | } 2677 | 2678 | if (F.SpecificElementExtensions) { 2679 | for (var tag in Element.Methods.ByTag) { 2680 | var klass = findDOMClass(tag); 2681 | if (Object.isUndefined(klass)) continue; 2682 | copy(T[tag], klass.prototype); 2683 | } 2684 | } 2685 | 2686 | Object.extend(Element, Element.Methods); 2687 | delete Element.ByTag; 2688 | 2689 | if (Element.extend.refresh) Element.extend.refresh(); 2690 | Element.cache = { }; 2691 | }; 2692 | 2693 | document.viewport = { 2694 | getDimensions: function() { 2695 | var dimensions = { }; 2696 | var B = Prototype.Browser; 2697 | $w('width height').each(function(d) { 2698 | var D = d.capitalize(); 2699 | dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : 2700 | (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; 2701 | }); 2702 | return dimensions; 2703 | }, 2704 | 2705 | getWidth: function() { 2706 | return this.getDimensions().width; 2707 | }, 2708 | 2709 | getHeight: function() { 2710 | return this.getDimensions().height; 2711 | }, 2712 | 2713 | getScrollOffsets: function() { 2714 | return Element._returnOffset( 2715 | window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, 2716 | window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); 2717 | } 2718 | }; 2719 | /* Portions of the Selector class are derived from Jack Slocum’s DomQuery, 2720 | * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style 2721 | * license. Please see http://www.yui-ext.com/ for more information. */ 2722 | 2723 | var Selector = Class.create({ 2724 | initialize: function(expression) { 2725 | this.expression = expression.strip(); 2726 | this.compileMatcher(); 2727 | }, 2728 | 2729 | shouldUseXPath: function() { 2730 | if (!Prototype.BrowserFeatures.XPath) return false; 2731 | 2732 | var e = this.expression; 2733 | 2734 | // Safari 3 chokes on :*-of-type and :empty 2735 | if (Prototype.Browser.WebKit && 2736 | (e.include("-of-type") || e.include(":empty"))) 2737 | return false; 2738 | 2739 | // XPath can't do namespaced attributes, nor can it read 2740 | // the "checked" property from DOM nodes 2741 | if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) 2742 | return false; 2743 | 2744 | return true; 2745 | }, 2746 | 2747 | compileMatcher: function() { 2748 | if (this.shouldUseXPath()) 2749 | return this.compileXPathMatcher(); 2750 | 2751 | var e = this.expression, ps = Selector.patterns, h = Selector.handlers, 2752 | c = Selector.criteria, le, p, m; 2753 | 2754 | if (Selector._cache[e]) { 2755 | this.matcher = Selector._cache[e]; 2756 | return; 2757 | } 2758 | 2759 | this.matcher = ["this.matcher = function(root) {", 2760 | "var r = root, h = Selector.handlers, c = false, n;"]; 2761 | 2762 | while (e && le != e && (/\S/).test(e)) { 2763 | le = e; 2764 | for (var i in ps) { 2765 | p = ps[i]; 2766 | if (m = e.match(p)) { 2767 | this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : 2768 | new Template(c[i]).evaluate(m)); 2769 | e = e.replace(m[0], ''); 2770 | break; 2771 | } 2772 | } 2773 | } 2774 | 2775 | this.matcher.push("return h.unique(n);\n}"); 2776 | eval(this.matcher.join('\n')); 2777 | Selector._cache[this.expression] = this.matcher; 2778 | }, 2779 | 2780 | compileXPathMatcher: function() { 2781 | var e = this.expression, ps = Selector.patterns, 2782 | x = Selector.xpath, le, m; 2783 | 2784 | if (Selector._cache[e]) { 2785 | this.xpath = Selector._cache[e]; return; 2786 | } 2787 | 2788 | this.matcher = ['.//*']; 2789 | while (e && le != e && (/\S/).test(e)) { 2790 | le = e; 2791 | for (var i in ps) { 2792 | if (m = e.match(ps[i])) { 2793 | this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : 2794 | new Template(x[i]).evaluate(m)); 2795 | e = e.replace(m[0], ''); 2796 | break; 2797 | } 2798 | } 2799 | } 2800 | 2801 | this.xpath = this.matcher.join(''); 2802 | Selector._cache[this.expression] = this.xpath; 2803 | }, 2804 | 2805 | findElements: function(root) { 2806 | root = root || document; 2807 | if (this.xpath) return document._getElementsByXPath(this.xpath, root); 2808 | return this.matcher(root); 2809 | }, 2810 | 2811 | match: function(element) { 2812 | this.tokens = []; 2813 | 2814 | var e = this.expression, ps = Selector.patterns, as = Selector.assertions; 2815 | var le, p, m; 2816 | 2817 | while (e && le !== e && (/\S/).test(e)) { 2818 | le = e; 2819 | for (var i in ps) { 2820 | p = ps[i]; 2821 | if (m = e.match(p)) { 2822 | // use the Selector.assertions methods unless the selector 2823 | // is too complex. 2824 | if (as[i]) { 2825 | this.tokens.push([i, Object.clone(m)]); 2826 | e = e.replace(m[0], ''); 2827 | } else { 2828 | // reluctantly do a document-wide search 2829 | // and look for a match in the array 2830 | return this.findElements(document).include(element); 2831 | } 2832 | } 2833 | } 2834 | } 2835 | 2836 | var match = true, name, matches; 2837 | for (var i = 0, token; token = this.tokens[i]; i++) { 2838 | name = token[0], matches = token[1]; 2839 | if (!Selector.assertions[name](element, matches)) { 2840 | match = false; break; 2841 | } 2842 | } 2843 | 2844 | return match; 2845 | }, 2846 | 2847 | toString: function() { 2848 | return this.expression; 2849 | }, 2850 | 2851 | inspect: function() { 2852 | return "#"; 2853 | } 2854 | }); 2855 | 2856 | Object.extend(Selector, { 2857 | _cache: { }, 2858 | 2859 | xpath: { 2860 | descendant: "//*", 2861 | child: "/*", 2862 | adjacent: "/following-sibling::*[1]", 2863 | laterSibling: '/following-sibling::*', 2864 | tagName: function(m) { 2865 | if (m[1] == '*') return ''; 2866 | return "[local-name()='" + m[1].toLowerCase() + 2867 | "' or local-name()='" + m[1].toUpperCase() + "']"; 2868 | }, 2869 | className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", 2870 | id: "[@id='#{1}']", 2871 | attrPresence: function(m) { 2872 | m[1] = m[1].toLowerCase(); 2873 | return new Template("[@#{1}]").evaluate(m); 2874 | }, 2875 | attr: function(m) { 2876 | m[1] = m[1].toLowerCase(); 2877 | m[3] = m[5] || m[6]; 2878 | return new Template(Selector.xpath.operators[m[2]]).evaluate(m); 2879 | }, 2880 | pseudo: function(m) { 2881 | var h = Selector.xpath.pseudos[m[1]]; 2882 | if (!h) return ''; 2883 | if (Object.isFunction(h)) return h(m); 2884 | return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); 2885 | }, 2886 | operators: { 2887 | '=': "[@#{1}='#{3}']", 2888 | '!=': "[@#{1}!='#{3}']", 2889 | '^=': "[starts-with(@#{1}, '#{3}')]", 2890 | '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 2891 | '*=': "[contains(@#{1}, '#{3}')]", 2892 | '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", 2893 | '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" 2894 | }, 2895 | pseudos: { 2896 | 'first-child': '[not(preceding-sibling::*)]', 2897 | 'last-child': '[not(following-sibling::*)]', 2898 | 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 2899 | 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 2900 | 'checked': "[@checked]", 2901 | 'disabled': "[@disabled]", 2902 | 'enabled': "[not(@disabled)]", 2903 | 'not': function(m) { 2904 | var e = m[6], p = Selector.patterns, 2905 | x = Selector.xpath, le, v; 2906 | 2907 | var exclusion = []; 2908 | while (e && le != e && (/\S/).test(e)) { 2909 | le = e; 2910 | for (var i in p) { 2911 | if (m = e.match(p[i])) { 2912 | v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); 2913 | exclusion.push("(" + v.substring(1, v.length - 1) + ")"); 2914 | e = e.replace(m[0], ''); 2915 | break; 2916 | } 2917 | } 2918 | } 2919 | return "[not(" + exclusion.join(" and ") + ")]"; 2920 | }, 2921 | 'nth-child': function(m) { 2922 | return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); 2923 | }, 2924 | 'nth-last-child': function(m) { 2925 | return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); 2926 | }, 2927 | 'nth-of-type': function(m) { 2928 | return Selector.xpath.pseudos.nth("position() ", m); 2929 | }, 2930 | 'nth-last-of-type': function(m) { 2931 | return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); 2932 | }, 2933 | 'first-of-type': function(m) { 2934 | m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); 2935 | }, 2936 | 'last-of-type': function(m) { 2937 | m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); 2938 | }, 2939 | 'only-of-type': function(m) { 2940 | var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); 2941 | }, 2942 | nth: function(fragment, m) { 2943 | var mm, formula = m[6], predicate; 2944 | if (formula == 'even') formula = '2n+0'; 2945 | if (formula == 'odd') formula = '2n+1'; 2946 | if (mm = formula.match(/^(\d+)$/)) // digit only 2947 | return '[' + fragment + "= " + mm[1] + ']'; 2948 | if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 2949 | if (mm[1] == "-") mm[1] = -1; 2950 | var a = mm[1] ? Number(mm[1]) : 1; 2951 | var b = mm[2] ? Number(mm[2]) : 0; 2952 | predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + 2953 | "((#{fragment} - #{b}) div #{a} >= 0)]"; 2954 | return new Template(predicate).evaluate({ 2955 | fragment: fragment, a: a, b: b }); 2956 | } 2957 | } 2958 | } 2959 | }, 2960 | 2961 | criteria: { 2962 | tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', 2963 | className: 'n = h.className(n, r, "#{1}", c); c = false;', 2964 | id: 'n = h.id(n, r, "#{1}", c); c = false;', 2965 | attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', 2966 | attr: function(m) { 2967 | m[3] = (m[5] || m[6]); 2968 | return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); 2969 | }, 2970 | pseudo: function(m) { 2971 | if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); 2972 | return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); 2973 | }, 2974 | descendant: 'c = "descendant";', 2975 | child: 'c = "child";', 2976 | adjacent: 'c = "adjacent";', 2977 | laterSibling: 'c = "laterSibling";' 2978 | }, 2979 | 2980 | patterns: { 2981 | // combinators must be listed first 2982 | // (and descendant needs to be last combinator) 2983 | laterSibling: /^\s*~\s*/, 2984 | child: /^\s*>\s*/, 2985 | adjacent: /^\s*\+\s*/, 2986 | descendant: /^\s/, 2987 | 2988 | // selectors follow 2989 | tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, 2990 | id: /^#([\w\-\*]+)(\b|$)/, 2991 | className: /^\.([\w\-\*]+)(\b|$)/, 2992 | pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, 2993 | attrPresence: /^\[([\w]+)\]/, 2994 | attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ 2995 | }, 2996 | 2997 | // for Selector.match and Element#match 2998 | assertions: { 2999 | tagName: function(element, matches) { 3000 | return matches[1].toUpperCase() == element.tagName.toUpperCase(); 3001 | }, 3002 | 3003 | className: function(element, matches) { 3004 | return Element.hasClassName(element, matches[1]); 3005 | }, 3006 | 3007 | id: function(element, matches) { 3008 | return element.id === matches[1]; 3009 | }, 3010 | 3011 | attrPresence: function(element, matches) { 3012 | return Element.hasAttribute(element, matches[1]); 3013 | }, 3014 | 3015 | attr: function(element, matches) { 3016 | var nodeValue = Element.readAttribute(element, matches[1]); 3017 | return Selector.operators[matches[2]](nodeValue, matches[3]); 3018 | } 3019 | }, 3020 | 3021 | handlers: { 3022 | // UTILITY FUNCTIONS 3023 | // joins two collections 3024 | concat: function(a, b) { 3025 | for (var i = 0, node; node = b[i]; i++) 3026 | a.push(node); 3027 | return a; 3028 | }, 3029 | 3030 | // marks an array of nodes for counting 3031 | mark: function(nodes) { 3032 | for (var i = 0, node; node = nodes[i]; i++) 3033 | node._counted = true; 3034 | return nodes; 3035 | }, 3036 | 3037 | unmark: function(nodes) { 3038 | for (var i = 0, node; node = nodes[i]; i++) 3039 | node._counted = undefined; 3040 | return nodes; 3041 | }, 3042 | 3043 | // mark each child node with its position (for nth calls) 3044 | // "ofType" flag indicates whether we're indexing for nth-of-type 3045 | // rather than nth-child 3046 | index: function(parentNode, reverse, ofType) { 3047 | parentNode._counted = true; 3048 | if (reverse) { 3049 | for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { 3050 | var node = nodes[i]; 3051 | if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 3052 | } 3053 | } else { 3054 | for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) 3055 | if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 3056 | } 3057 | }, 3058 | 3059 | // filters out duplicates and extends all nodes 3060 | unique: function(nodes) { 3061 | if (nodes.length == 0) return nodes; 3062 | var results = [], n; 3063 | for (var i = 0, l = nodes.length; i < l; i++) 3064 | if (!(n = nodes[i])._counted) { 3065 | n._counted = true; 3066 | results.push(Element.extend(n)); 3067 | } 3068 | return Selector.handlers.unmark(results); 3069 | }, 3070 | 3071 | // COMBINATOR FUNCTIONS 3072 | descendant: function(nodes) { 3073 | var h = Selector.handlers; 3074 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3075 | h.concat(results, node.getElementsByTagName('*')); 3076 | return results; 3077 | }, 3078 | 3079 | child: function(nodes) { 3080 | var h = Selector.handlers; 3081 | for (var i = 0, results = [], node; node = nodes[i]; i++) { 3082 | for (var j = 0, child; child = node.childNodes[j]; j++) 3083 | if (child.nodeType == 1 && child.tagName != '!') results.push(child); 3084 | } 3085 | return results; 3086 | }, 3087 | 3088 | adjacent: function(nodes) { 3089 | for (var i = 0, results = [], node; node = nodes[i]; i++) { 3090 | var next = this.nextElementSibling(node); 3091 | if (next) results.push(next); 3092 | } 3093 | return results; 3094 | }, 3095 | 3096 | laterSibling: function(nodes) { 3097 | var h = Selector.handlers; 3098 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3099 | h.concat(results, Element.nextSiblings(node)); 3100 | return results; 3101 | }, 3102 | 3103 | nextElementSibling: function(node) { 3104 | while (node = node.nextSibling) 3105 | if (node.nodeType == 1) return node; 3106 | return null; 3107 | }, 3108 | 3109 | previousElementSibling: function(node) { 3110 | while (node = node.previousSibling) 3111 | if (node.nodeType == 1) return node; 3112 | return null; 3113 | }, 3114 | 3115 | // TOKEN FUNCTIONS 3116 | tagName: function(nodes, root, tagName, combinator) { 3117 | tagName = tagName.toUpperCase(); 3118 | var results = [], h = Selector.handlers; 3119 | if (nodes) { 3120 | if (combinator) { 3121 | // fastlane for ordinary descendant combinators 3122 | if (combinator == "descendant") { 3123 | for (var i = 0, node; node = nodes[i]; i++) 3124 | h.concat(results, node.getElementsByTagName(tagName)); 3125 | return results; 3126 | } else nodes = this[combinator](nodes); 3127 | if (tagName == "*") return nodes; 3128 | } 3129 | for (var i = 0, node; node = nodes[i]; i++) 3130 | if (node.tagName.toUpperCase() == tagName) results.push(node); 3131 | return results; 3132 | } else return root.getElementsByTagName(tagName); 3133 | }, 3134 | 3135 | id: function(nodes, root, id, combinator) { 3136 | var targetNode = $(id), h = Selector.handlers; 3137 | if (!targetNode) return []; 3138 | if (!nodes && root == document) return [targetNode]; 3139 | if (nodes) { 3140 | if (combinator) { 3141 | if (combinator == 'child') { 3142 | for (var i = 0, node; node = nodes[i]; i++) 3143 | if (targetNode.parentNode == node) return [targetNode]; 3144 | } else if (combinator == 'descendant') { 3145 | for (var i = 0, node; node = nodes[i]; i++) 3146 | if (Element.descendantOf(targetNode, node)) return [targetNode]; 3147 | } else if (combinator == 'adjacent') { 3148 | for (var i = 0, node; node = nodes[i]; i++) 3149 | if (Selector.handlers.previousElementSibling(targetNode) == node) 3150 | return [targetNode]; 3151 | } else nodes = h[combinator](nodes); 3152 | } 3153 | for (var i = 0, node; node = nodes[i]; i++) 3154 | if (node == targetNode) return [targetNode]; 3155 | return []; 3156 | } 3157 | return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; 3158 | }, 3159 | 3160 | className: function(nodes, root, className, combinator) { 3161 | if (nodes && combinator) nodes = this[combinator](nodes); 3162 | return Selector.handlers.byClassName(nodes, root, className); 3163 | }, 3164 | 3165 | byClassName: function(nodes, root, className) { 3166 | if (!nodes) nodes = Selector.handlers.descendant([root]); 3167 | var needle = ' ' + className + ' '; 3168 | for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 3169 | nodeClassName = node.className; 3170 | if (nodeClassName.length == 0) continue; 3171 | if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 3172 | results.push(node); 3173 | } 3174 | return results; 3175 | }, 3176 | 3177 | attrPresence: function(nodes, root, attr) { 3178 | if (!nodes) nodes = root.getElementsByTagName("*"); 3179 | var results = []; 3180 | for (var i = 0, node; node = nodes[i]; i++) 3181 | if (Element.hasAttribute(node, attr)) results.push(node); 3182 | return results; 3183 | }, 3184 | 3185 | attr: function(nodes, root, attr, value, operator) { 3186 | if (!nodes) nodes = root.getElementsByTagName("*"); 3187 | var handler = Selector.operators[operator], results = []; 3188 | for (var i = 0, node; node = nodes[i]; i++) { 3189 | var nodeValue = Element.readAttribute(node, attr); 3190 | if (nodeValue === null) continue; 3191 | if (handler(nodeValue, value)) results.push(node); 3192 | } 3193 | return results; 3194 | }, 3195 | 3196 | pseudo: function(nodes, name, value, root, combinator) { 3197 | if (nodes && combinator) nodes = this[combinator](nodes); 3198 | if (!nodes) nodes = root.getElementsByTagName("*"); 3199 | return Selector.pseudos[name](nodes, value, root); 3200 | } 3201 | }, 3202 | 3203 | pseudos: { 3204 | 'first-child': function(nodes, value, root) { 3205 | for (var i = 0, results = [], node; node = nodes[i]; i++) { 3206 | if (Selector.handlers.previousElementSibling(node)) continue; 3207 | results.push(node); 3208 | } 3209 | return results; 3210 | }, 3211 | 'last-child': function(nodes, value, root) { 3212 | for (var i = 0, results = [], node; node = nodes[i]; i++) { 3213 | if (Selector.handlers.nextElementSibling(node)) continue; 3214 | results.push(node); 3215 | } 3216 | return results; 3217 | }, 3218 | 'only-child': function(nodes, value, root) { 3219 | var h = Selector.handlers; 3220 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3221 | if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 3222 | results.push(node); 3223 | return results; 3224 | }, 3225 | 'nth-child': function(nodes, formula, root) { 3226 | return Selector.pseudos.nth(nodes, formula, root); 3227 | }, 3228 | 'nth-last-child': function(nodes, formula, root) { 3229 | return Selector.pseudos.nth(nodes, formula, root, true); 3230 | }, 3231 | 'nth-of-type': function(nodes, formula, root) { 3232 | return Selector.pseudos.nth(nodes, formula, root, false, true); 3233 | }, 3234 | 'nth-last-of-type': function(nodes, formula, root) { 3235 | return Selector.pseudos.nth(nodes, formula, root, true, true); 3236 | }, 3237 | 'first-of-type': function(nodes, formula, root) { 3238 | return Selector.pseudos.nth(nodes, "1", root, false, true); 3239 | }, 3240 | 'last-of-type': function(nodes, formula, root) { 3241 | return Selector.pseudos.nth(nodes, "1", root, true, true); 3242 | }, 3243 | 'only-of-type': function(nodes, formula, root) { 3244 | var p = Selector.pseudos; 3245 | return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); 3246 | }, 3247 | 3248 | // handles the an+b logic 3249 | getIndices: function(a, b, total) { 3250 | if (a == 0) return b > 0 ? [b] : []; 3251 | return $R(1, total).inject([], function(memo, i) { 3252 | if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); 3253 | return memo; 3254 | }); 3255 | }, 3256 | 3257 | // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type 3258 | nth: function(nodes, formula, root, reverse, ofType) { 3259 | if (nodes.length == 0) return []; 3260 | if (formula == 'even') formula = '2n+0'; 3261 | if (formula == 'odd') formula = '2n+1'; 3262 | var h = Selector.handlers, results = [], indexed = [], m; 3263 | h.mark(nodes); 3264 | for (var i = 0, node; node = nodes[i]; i++) { 3265 | if (!node.parentNode._counted) { 3266 | h.index(node.parentNode, reverse, ofType); 3267 | indexed.push(node.parentNode); 3268 | } 3269 | } 3270 | if (formula.match(/^\d+$/)) { // just a number 3271 | formula = Number(formula); 3272 | for (var i = 0, node; node = nodes[i]; i++) 3273 | if (node.nodeIndex == formula) results.push(node); 3274 | } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b 3275 | if (m[1] == "-") m[1] = -1; 3276 | var a = m[1] ? Number(m[1]) : 1; 3277 | var b = m[2] ? Number(m[2]) : 0; 3278 | var indices = Selector.pseudos.getIndices(a, b, nodes.length); 3279 | for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { 3280 | for (var j = 0; j < l; j++) 3281 | if (node.nodeIndex == indices[j]) results.push(node); 3282 | } 3283 | } 3284 | h.unmark(nodes); 3285 | h.unmark(indexed); 3286 | return results; 3287 | }, 3288 | 3289 | 'empty': function(nodes, value, root) { 3290 | for (var i = 0, results = [], node; node = nodes[i]; i++) { 3291 | // IE treats comments as element nodes 3292 | if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; 3293 | results.push(node); 3294 | } 3295 | return results; 3296 | }, 3297 | 3298 | 'not': function(nodes, selector, root) { 3299 | var h = Selector.handlers, selectorType, m; 3300 | var exclusions = new Selector(selector).findElements(root); 3301 | h.mark(exclusions); 3302 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3303 | if (!node._counted) results.push(node); 3304 | h.unmark(exclusions); 3305 | return results; 3306 | }, 3307 | 3308 | 'enabled': function(nodes, value, root) { 3309 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3310 | if (!node.disabled) results.push(node); 3311 | return results; 3312 | }, 3313 | 3314 | 'disabled': function(nodes, value, root) { 3315 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3316 | if (node.disabled) results.push(node); 3317 | return results; 3318 | }, 3319 | 3320 | 'checked': function(nodes, value, root) { 3321 | for (var i = 0, results = [], node; node = nodes[i]; i++) 3322 | if (node.checked) results.push(node); 3323 | return results; 3324 | } 3325 | }, 3326 | 3327 | operators: { 3328 | '=': function(nv, v) { return nv == v; }, 3329 | '!=': function(nv, v) { return nv != v; }, 3330 | '^=': function(nv, v) { return nv.startsWith(v); }, 3331 | '$=': function(nv, v) { return nv.endsWith(v); }, 3332 | '*=': function(nv, v) { return nv.include(v); }, 3333 | '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, 3334 | '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } 3335 | }, 3336 | 3337 | matchElements: function(elements, expression) { 3338 | var matches = new Selector(expression).findElements(), h = Selector.handlers; 3339 | h.mark(matches); 3340 | for (var i = 0, results = [], element; element = elements[i]; i++) 3341 | if (element._counted) results.push(element); 3342 | h.unmark(matches); 3343 | return results; 3344 | }, 3345 | 3346 | findElement: function(elements, expression, index) { 3347 | if (Object.isNumber(expression)) { 3348 | index = expression; expression = false; 3349 | } 3350 | return Selector.matchElements(elements, expression || '*')[index || 0]; 3351 | }, 3352 | 3353 | findChildElements: function(element, expressions) { 3354 | var exprs = expressions.join(','); 3355 | expressions = []; 3356 | exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { 3357 | expressions.push(m[1].strip()); 3358 | }); 3359 | var results = [], h = Selector.handlers; 3360 | for (var i = 0, l = expressions.length, selector; i < l; i++) { 3361 | selector = new Selector(expressions[i].strip()); 3362 | h.concat(results, selector.findElements(element)); 3363 | } 3364 | return (l > 1) ? h.unique(results) : results; 3365 | } 3366 | }); 3367 | 3368 | if (Prototype.Browser.IE) { 3369 | // IE returns comment nodes on getElementsByTagName("*"). 3370 | // Filter them out. 3371 | Selector.handlers.concat = function(a, b) { 3372 | for (var i = 0, node; node = b[i]; i++) 3373 | if (node.tagName !== "!") a.push(node); 3374 | return a; 3375 | }; 3376 | } 3377 | 3378 | function $$() { 3379 | return Selector.findChildElements(document, $A(arguments)); 3380 | } 3381 | var Form = { 3382 | reset: function(form) { 3383 | $(form).reset(); 3384 | return form; 3385 | }, 3386 | 3387 | serializeElements: function(elements, options) { 3388 | if (typeof options != 'object') options = { hash: !!options }; 3389 | else if (Object.isUndefined(options.hash)) options.hash = true; 3390 | var key, value, submitted = false, submit = options.submit; 3391 | 3392 | var data = elements.inject({ }, function(result, element) { 3393 | if (!element.disabled && element.name) { 3394 | key = element.name; value = $(element).getValue(); 3395 | if (value != null && (element.type != 'submit' || (!submitted && 3396 | submit !== false && (!submit || key == submit) && (submitted = true)))) { 3397 | if (key in result) { 3398 | // a key is already present; construct an array of values 3399 | if (!Object.isArray(result[key])) result[key] = [result[key]]; 3400 | result[key].push(value); 3401 | } 3402 | else result[key] = value; 3403 | } 3404 | } 3405 | return result; 3406 | }); 3407 | 3408 | return options.hash ? data : Object.toQueryString(data); 3409 | } 3410 | }; 3411 | 3412 | Form.Methods = { 3413 | serialize: function(form, options) { 3414 | return Form.serializeElements(Form.getElements(form), options); 3415 | }, 3416 | 3417 | getElements: function(form) { 3418 | return $A($(form).getElementsByTagName('*')).inject([], 3419 | function(elements, child) { 3420 | if (Form.Element.Serializers[child.tagName.toLowerCase()]) 3421 | elements.push(Element.extend(child)); 3422 | return elements; 3423 | } 3424 | ); 3425 | }, 3426 | 3427 | getInputs: function(form, typeName, name) { 3428 | form = $(form); 3429 | var inputs = form.getElementsByTagName('input'); 3430 | 3431 | if (!typeName && !name) return $A(inputs).map(Element.extend); 3432 | 3433 | for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { 3434 | var input = inputs[i]; 3435 | if ((typeName && input.type != typeName) || (name && input.name != name)) 3436 | continue; 3437 | matchingInputs.push(Element.extend(input)); 3438 | } 3439 | 3440 | return matchingInputs; 3441 | }, 3442 | 3443 | disable: function(form) { 3444 | form = $(form); 3445 | Form.getElements(form).invoke('disable'); 3446 | return form; 3447 | }, 3448 | 3449 | enable: function(form) { 3450 | form = $(form); 3451 | Form.getElements(form).invoke('enable'); 3452 | return form; 3453 | }, 3454 | 3455 | findFirstElement: function(form) { 3456 | var elements = $(form).getElements().findAll(function(element) { 3457 | return 'hidden' != element.type && !element.disabled; 3458 | }); 3459 | var firstByIndex = elements.findAll(function(element) { 3460 | return element.hasAttribute('tabIndex') && element.tabIndex >= 0; 3461 | }).sortBy(function(element) { return element.tabIndex }).first(); 3462 | 3463 | return firstByIndex ? firstByIndex : elements.find(function(element) { 3464 | return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); 3465 | }); 3466 | }, 3467 | 3468 | focusFirstElement: function(form) { 3469 | form = $(form); 3470 | form.findFirstElement().activate(); 3471 | return form; 3472 | }, 3473 | 3474 | request: function(form, options) { 3475 | form = $(form), options = Object.clone(options || { }); 3476 | 3477 | var params = options.parameters, action = form.readAttribute('action') || ''; 3478 | if (action.blank()) action = window.location.href; 3479 | options.parameters = form.serialize(true); 3480 | 3481 | if (params) { 3482 | if (Object.isString(params)) params = params.toQueryParams(); 3483 | Object.extend(options.parameters, params); 3484 | } 3485 | 3486 | if (form.hasAttribute('method') && !options.method) 3487 | options.method = form.method; 3488 | 3489 | return new Ajax.Request(action, options); 3490 | } 3491 | }; 3492 | 3493 | /*--------------------------------------------------------------------------*/ 3494 | 3495 | Form.Element = { 3496 | focus: function(element) { 3497 | $(element).focus(); 3498 | return element; 3499 | }, 3500 | 3501 | select: function(element) { 3502 | $(element).select(); 3503 | return element; 3504 | } 3505 | }; 3506 | 3507 | Form.Element.Methods = { 3508 | serialize: function(element) { 3509 | element = $(element); 3510 | if (!element.disabled && element.name) { 3511 | var value = element.getValue(); 3512 | if (value != undefined) { 3513 | var pair = { }; 3514 | pair[element.name] = value; 3515 | return Object.toQueryString(pair); 3516 | } 3517 | } 3518 | return ''; 3519 | }, 3520 | 3521 | getValue: function(element) { 3522 | element = $(element); 3523 | var method = element.tagName.toLowerCase(); 3524 | return Form.Element.Serializers[method](element); 3525 | }, 3526 | 3527 | setValue: function(element, value) { 3528 | element = $(element); 3529 | var method = element.tagName.toLowerCase(); 3530 | Form.Element.Serializers[method](element, value); 3531 | return element; 3532 | }, 3533 | 3534 | clear: function(element) { 3535 | $(element).value = ''; 3536 | return element; 3537 | }, 3538 | 3539 | present: function(element) { 3540 | return $(element).value != ''; 3541 | }, 3542 | 3543 | activate: function(element) { 3544 | element = $(element); 3545 | try { 3546 | element.focus(); 3547 | if (element.select && (element.tagName.toLowerCase() != 'input' || 3548 | !['button', 'reset', 'submit'].include(element.type))) 3549 | element.select(); 3550 | } catch (e) { } 3551 | return element; 3552 | }, 3553 | 3554 | disable: function(element) { 3555 | element = $(element); 3556 | element.blur(); 3557 | element.disabled = true; 3558 | return element; 3559 | }, 3560 | 3561 | enable: function(element) { 3562 | element = $(element); 3563 | element.disabled = false; 3564 | return element; 3565 | } 3566 | }; 3567 | 3568 | /*--------------------------------------------------------------------------*/ 3569 | 3570 | var Field = Form.Element; 3571 | var $F = Form.Element.Methods.getValue; 3572 | 3573 | /*--------------------------------------------------------------------------*/ 3574 | 3575 | Form.Element.Serializers = { 3576 | input: function(element, value) { 3577 | switch (element.type.toLowerCase()) { 3578 | case 'checkbox': 3579 | case 'radio': 3580 | return Form.Element.Serializers.inputSelector(element, value); 3581 | default: 3582 | return Form.Element.Serializers.textarea(element, value); 3583 | } 3584 | }, 3585 | 3586 | inputSelector: function(element, value) { 3587 | if (Object.isUndefined(value)) return element.checked ? element.value : null; 3588 | else element.checked = !!value; 3589 | }, 3590 | 3591 | textarea: function(element, value) { 3592 | if (Object.isUndefined(value)) return element.value; 3593 | else element.value = value; 3594 | }, 3595 | 3596 | select: function(element, index) { 3597 | if (Object.isUndefined(index)) 3598 | return this[element.type == 'select-one' ? 3599 | 'selectOne' : 'selectMany'](element); 3600 | else { 3601 | var opt, value, single = !Object.isArray(index); 3602 | for (var i = 0, length = element.length; i < length; i++) { 3603 | opt = element.options[i]; 3604 | value = this.optionValue(opt); 3605 | if (single) { 3606 | if (value == index) { 3607 | opt.selected = true; 3608 | return; 3609 | } 3610 | } 3611 | else opt.selected = index.include(value); 3612 | } 3613 | } 3614 | }, 3615 | 3616 | selectOne: function(element) { 3617 | var index = element.selectedIndex; 3618 | return index >= 0 ? this.optionValue(element.options[index]) : null; 3619 | }, 3620 | 3621 | selectMany: function(element) { 3622 | var values, length = element.length; 3623 | if (!length) return null; 3624 | 3625 | for (var i = 0, values = []; i < length; i++) { 3626 | var opt = element.options[i]; 3627 | if (opt.selected) values.push(this.optionValue(opt)); 3628 | } 3629 | return values; 3630 | }, 3631 | 3632 | optionValue: function(opt) { 3633 | // extend element because hasAttribute may not be native 3634 | return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; 3635 | } 3636 | }; 3637 | 3638 | /*--------------------------------------------------------------------------*/ 3639 | 3640 | Abstract.TimedObserver = Class.create(PeriodicalExecuter, { 3641 | initialize: function($super, element, frequency, callback) { 3642 | $super(callback, frequency); 3643 | this.element = $(element); 3644 | this.lastValue = this.getValue(); 3645 | }, 3646 | 3647 | execute: function() { 3648 | var value = this.getValue(); 3649 | if (Object.isString(this.lastValue) && Object.isString(value) ? 3650 | this.lastValue != value : String(this.lastValue) != String(value)) { 3651 | this.callback(this.element, value); 3652 | this.lastValue = value; 3653 | } 3654 | } 3655 | }); 3656 | 3657 | Form.Element.Observer = Class.create(Abstract.TimedObserver, { 3658 | getValue: function() { 3659 | return Form.Element.getValue(this.element); 3660 | } 3661 | }); 3662 | 3663 | Form.Observer = Class.create(Abstract.TimedObserver, { 3664 | getValue: function() { 3665 | return Form.serialize(this.element); 3666 | } 3667 | }); 3668 | 3669 | /*--------------------------------------------------------------------------*/ 3670 | 3671 | Abstract.EventObserver = Class.create({ 3672 | initialize: function(element, callback) { 3673 | this.element = $(element); 3674 | this.callback = callback; 3675 | 3676 | this.lastValue = this.getValue(); 3677 | if (this.element.tagName.toLowerCase() == 'form') 3678 | this.registerFormCallbacks(); 3679 | else 3680 | this.registerCallback(this.element); 3681 | }, 3682 | 3683 | onElementEvent: function() { 3684 | var value = this.getValue(); 3685 | if (this.lastValue != value) { 3686 | this.callback(this.element, value); 3687 | this.lastValue = value; 3688 | } 3689 | }, 3690 | 3691 | registerFormCallbacks: function() { 3692 | Form.getElements(this.element).each(this.registerCallback, this); 3693 | }, 3694 | 3695 | registerCallback: function(element) { 3696 | if (element.type) { 3697 | switch (element.type.toLowerCase()) { 3698 | case 'checkbox': 3699 | case 'radio': 3700 | Event.observe(element, 'click', this.onElementEvent.bind(this)); 3701 | break; 3702 | default: 3703 | Event.observe(element, 'change', this.onElementEvent.bind(this)); 3704 | break; 3705 | } 3706 | } 3707 | } 3708 | }); 3709 | 3710 | Form.Element.EventObserver = Class.create(Abstract.EventObserver, { 3711 | getValue: function() { 3712 | return Form.Element.getValue(this.element); 3713 | } 3714 | }); 3715 | 3716 | Form.EventObserver = Class.create(Abstract.EventObserver, { 3717 | getValue: function() { 3718 | return Form.serialize(this.element); 3719 | } 3720 | }); 3721 | if (!window.Event) var Event = { }; 3722 | 3723 | Object.extend(Event, { 3724 | KEY_BACKSPACE: 8, 3725 | KEY_TAB: 9, 3726 | KEY_RETURN: 13, 3727 | KEY_ESC: 27, 3728 | KEY_LEFT: 37, 3729 | KEY_UP: 38, 3730 | KEY_RIGHT: 39, 3731 | KEY_DOWN: 40, 3732 | KEY_DELETE: 46, 3733 | KEY_HOME: 36, 3734 | KEY_END: 35, 3735 | KEY_PAGEUP: 33, 3736 | KEY_PAGEDOWN: 34, 3737 | KEY_INSERT: 45, 3738 | 3739 | cache: { }, 3740 | 3741 | relatedTarget: function(event) { 3742 | var element; 3743 | switch(event.type) { 3744 | case 'mouseover': element = event.fromElement; break; 3745 | case 'mouseout': element = event.toElement; break; 3746 | default: return null; 3747 | } 3748 | return Element.extend(element); 3749 | } 3750 | }); 3751 | 3752 | Event.Methods = (function() { 3753 | var isButton; 3754 | 3755 | if (Prototype.Browser.IE) { 3756 | var buttonMap = { 0: 1, 1: 4, 2: 2 }; 3757 | isButton = function(event, code) { 3758 | return event.button == buttonMap[code]; 3759 | }; 3760 | 3761 | } else if (Prototype.Browser.WebKit) { 3762 | isButton = function(event, code) { 3763 | switch (code) { 3764 | case 0: return event.which == 1 && !event.metaKey; 3765 | case 1: return event.which == 1 && event.metaKey; 3766 | default: return false; 3767 | } 3768 | }; 3769 | 3770 | } else { 3771 | isButton = function(event, code) { 3772 | return event.which ? (event.which === code + 1) : (event.button === code); 3773 | }; 3774 | } 3775 | 3776 | return { 3777 | isLeftClick: function(event) { return isButton(event, 0) }, 3778 | isMiddleClick: function(event) { return isButton(event, 1) }, 3779 | isRightClick: function(event) { return isButton(event, 2) }, 3780 | 3781 | element: function(event) { 3782 | var node = Event.extend(event).target; 3783 | return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); 3784 | }, 3785 | 3786 | findElement: function(event, expression) { 3787 | var element = Event.element(event); 3788 | if (!expression) return element; 3789 | var elements = [element].concat(element.ancestors()); 3790 | return Selector.findElement(elements, expression, 0); 3791 | }, 3792 | 3793 | pointer: function(event) { 3794 | return { 3795 | x: event.pageX || (event.clientX + 3796 | (document.documentElement.scrollLeft || document.body.scrollLeft)), 3797 | y: event.pageY || (event.clientY + 3798 | (document.documentElement.scrollTop || document.body.scrollTop)) 3799 | }; 3800 | }, 3801 | 3802 | pointerX: function(event) { return Event.pointer(event).x }, 3803 | pointerY: function(event) { return Event.pointer(event).y }, 3804 | 3805 | stop: function(event) { 3806 | Event.extend(event); 3807 | event.preventDefault(); 3808 | event.stopPropagation(); 3809 | event.stopped = true; 3810 | } 3811 | }; 3812 | })(); 3813 | 3814 | Event.extend = (function() { 3815 | var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { 3816 | m[name] = Event.Methods[name].methodize(); 3817 | return m; 3818 | }); 3819 | 3820 | if (Prototype.Browser.IE) { 3821 | Object.extend(methods, { 3822 | stopPropagation: function() { this.cancelBubble = true }, 3823 | preventDefault: function() { this.returnValue = false }, 3824 | inspect: function() { return "[object Event]" } 3825 | }); 3826 | 3827 | return function(event) { 3828 | if (!event) return false; 3829 | if (event._extendedByPrototype) return event; 3830 | 3831 | event._extendedByPrototype = Prototype.emptyFunction; 3832 | var pointer = Event.pointer(event); 3833 | Object.extend(event, { 3834 | target: event.srcElement, 3835 | relatedTarget: Event.relatedTarget(event), 3836 | pageX: pointer.x, 3837 | pageY: pointer.y 3838 | }); 3839 | return Object.extend(event, methods); 3840 | }; 3841 | 3842 | } else { 3843 | Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; 3844 | Object.extend(Event.prototype, methods); 3845 | return Prototype.K; 3846 | } 3847 | })(); 3848 | 3849 | Object.extend(Event, (function() { 3850 | var cache = Event.cache; 3851 | 3852 | function getEventID(element) { 3853 | if (element._eventID) return element._eventID; 3854 | arguments.callee.id = arguments.callee.id || 1; 3855 | return element._eventID = ++arguments.callee.id; 3856 | } 3857 | 3858 | function getDOMEventName(eventName) { 3859 | if (eventName && eventName.include(':')) return "dataavailable"; 3860 | return eventName; 3861 | } 3862 | 3863 | function getCacheForID(id) { 3864 | return cache[id] = cache[id] || { }; 3865 | } 3866 | 3867 | function getWrappersForEventName(id, eventName) { 3868 | var c = getCacheForID(id); 3869 | return c[eventName] = c[eventName] || []; 3870 | } 3871 | 3872 | function createWrapper(element, eventName, handler) { 3873 | var id = getEventID(element); 3874 | var c = getWrappersForEventName(id, eventName); 3875 | if (c.pluck("handler").include(handler)) return false; 3876 | 3877 | var wrapper = function(event) { 3878 | if (!Event || !Event.extend || 3879 | (event.eventName && event.eventName != eventName)) 3880 | return false; 3881 | 3882 | Event.extend(event); 3883 | handler.call(element, event) 3884 | }; 3885 | 3886 | wrapper.handler = handler; 3887 | c.push(wrapper); 3888 | return wrapper; 3889 | } 3890 | 3891 | function findWrapper(id, eventName, handler) { 3892 | var c = getWrappersForEventName(id, eventName); 3893 | return c.find(function(wrapper) { return wrapper.handler == handler }); 3894 | } 3895 | 3896 | function destroyWrapper(id, eventName, handler) { 3897 | var c = getCacheForID(id); 3898 | if (!c[eventName]) return false; 3899 | c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); 3900 | } 3901 | 3902 | function destroyCache() { 3903 | for (var id in cache) 3904 | for (var eventName in cache[id]) 3905 | cache[id][eventName] = null; 3906 | } 3907 | 3908 | if (window.attachEvent) { 3909 | window.attachEvent("onunload", destroyCache); 3910 | } 3911 | 3912 | return { 3913 | observe: function(element, eventName, handler) { 3914 | element = $(element); 3915 | var name = getDOMEventName(eventName); 3916 | 3917 | var wrapper = createWrapper(element, eventName, handler); 3918 | if (!wrapper) return element; 3919 | 3920 | if (element.addEventListener) { 3921 | element.addEventListener(name, wrapper, false); 3922 | } else { 3923 | element.attachEvent("on" + name, wrapper); 3924 | } 3925 | 3926 | return element; 3927 | }, 3928 | 3929 | stopObserving: function(element, eventName, handler) { 3930 | element = $(element); 3931 | var id = getEventID(element), name = getDOMEventName(eventName); 3932 | 3933 | if (!handler && eventName) { 3934 | getWrappersForEventName(id, eventName).each(function(wrapper) { 3935 | element.stopObserving(eventName, wrapper.handler); 3936 | }); 3937 | return element; 3938 | 3939 | } else if (!eventName) { 3940 | Object.keys(getCacheForID(id)).each(function(eventName) { 3941 | element.stopObserving(eventName); 3942 | }); 3943 | return element; 3944 | } 3945 | 3946 | var wrapper = findWrapper(id, eventName, handler); 3947 | if (!wrapper) return element; 3948 | 3949 | if (element.removeEventListener) { 3950 | element.removeEventListener(name, wrapper, false); 3951 | } else { 3952 | element.detachEvent("on" + name, wrapper); 3953 | } 3954 | 3955 | destroyWrapper(id, eventName, handler); 3956 | 3957 | return element; 3958 | }, 3959 | 3960 | fire: function(element, eventName, memo) { 3961 | element = $(element); 3962 | if (element == document && document.createEvent && !element.dispatchEvent) 3963 | element = document.documentElement; 3964 | 3965 | if (document.createEvent) { 3966 | var event = document.createEvent("HTMLEvents"); 3967 | event.initEvent("dataavailable", true, true); 3968 | } else { 3969 | var event = document.createEventObject(); 3970 | event.eventType = "ondataavailable"; 3971 | } 3972 | 3973 | event.eventName = eventName; 3974 | event.memo = memo || { }; 3975 | 3976 | if (document.createEvent) { 3977 | element.dispatchEvent(event); 3978 | } else { 3979 | element.fireEvent(event.eventType, event); 3980 | } 3981 | 3982 | return Event.extend(event); 3983 | } 3984 | }; 3985 | })()); 3986 | 3987 | Object.extend(Event, Event.Methods); 3988 | 3989 | Element.addMethods({ 3990 | fire: Event.fire, 3991 | observe: Event.observe, 3992 | stopObserving: Event.stopObserving 3993 | }); 3994 | 3995 | Object.extend(document, { 3996 | fire: Element.Methods.fire.methodize(), 3997 | observe: Element.Methods.observe.methodize(), 3998 | stopObserving: Element.Methods.stopObserving.methodize() 3999 | }); 4000 | 4001 | (function() { 4002 | /* Support for the DOMContentLoaded event is based on work by Dan Webb, 4003 | Matthias Miller, Dean Edwards and John Resig. */ 4004 | 4005 | var timer, fired = false; 4006 | 4007 | function fireContentLoadedEvent() { 4008 | if (fired) return; 4009 | if (timer) window.clearInterval(timer); 4010 | document.fire("dom:loaded"); 4011 | fired = true; 4012 | } 4013 | 4014 | if (document.addEventListener) { 4015 | if (Prototype.Browser.WebKit) { 4016 | timer = window.setInterval(function() { 4017 | if (/loaded|complete/.test(document.readyState)) 4018 | fireContentLoadedEvent(); 4019 | }, 0); 4020 | 4021 | Event.observe(window, "load", fireContentLoadedEvent); 4022 | 4023 | } else { 4024 | document.addEventListener("DOMContentLoaded", 4025 | fireContentLoadedEvent, false); 4026 | } 4027 | 4028 | } else { 4029 | document.write("