From 5ccf8cbefdc2ca99e5a6b4c0a616dbe934825ff4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 9 Sep 2009 23:18:45 -0500 Subject: [PATCH 001/502] Change all H4s in _method_ documentation blocks to H5s. (Start with H5 when using headings in a method doc block; start with H4 when using headings in a namespace/class doc block.) --- src/dom/dom.js | 2 +- src/dom/event.js | 20 ++++++++++---------- src/dom/form.js | 8 ++++---- src/lang/class.js | 2 +- src/lang/string.js | 12 ++++++------ 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 5713f0344..3fae168ad 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -320,7 +320,7 @@ Element.Methods = { * - `top` (as `element`'s first child) * - `bottom` (as `element`'s last child) * - *

Examples

+ *
Examples
* * Insert the given HTML at the bottom of the element (using the default): * diff --git a/src/dom/event.js b/src/dom/event.js index af7f01de2..88cab98c7 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -418,7 +418,7 @@ * * The examples in this documentation use the [[Element#observe]] form. * - *

The Handler

+ *
The Handler
* * Signature: * @@ -435,13 +435,13 @@ * Note that we used `this` to refer to the element, and that we received the `event` object * as a parameter (even on MSIE). * - *

It's All About Timing

+ *
It's All About Timing
* * One of the most common errors trying to observe events is trying to do it before the element * exists in the DOM. Don't try to observe elements until after the * [[document.observe dom:loaded]] event or `window` `load` event has been fired. * - *

Preventing the Default Event Action and Bubbling

+ *
Preventing the Default Event Action and Bubbling
* * If we want to stop the event (e.g., prevent its default action and stop it bubbling), we can * do so with the extended event object's [[Event#stop]] method: @@ -450,7 +450,7 @@ * event.stop(); * }); * - *

Finding the Element Where the Event Occurred

+ *
Finding the Element Where the Event Occurred
* * Since most events bubble from descendant elements up through the hierarchy until they're * handled, we can observe an event on a container rather than individual elements within the @@ -481,12 +481,12 @@ * record was clicked. [[Event#findElement]] finds the row that was clicked, and `this` refers * to the table we were observing. * - *

Stopping Observing the Event

+ *
Stopping Observing the Event
* * If we don't need to observe the event anymore, we can stop observing it with * [[Event.stopObserving]] (aka [[Element#stopObserving]]). * - *

Using an Instance Method as a Handler

+ *
Using an Instance Method as a Handler
* * If we want to use an instance method as a handler, we will probably want to use * [[Function#bind]] to set the handler's context; otherwise, the context will be lost and @@ -511,13 +511,13 @@ * details. There's also [[Function#bindAsEventListener]], which is handy for certain very * specific situations. (Normally, `bind` is all you need.) * - *

Side Notes

+ *
Side Notes
* * Although Prototype smooths out most of the differences between browsers, the fundamental * behavior of a browser implementation isn't changed. For example, the timing of the `change` * or `blur` events varies a bit from browser to browser. * - *

Changes in 1.6.x

+ *
Changes in 1.6.x
* * Prior to Prototype 1.6, `observe` supported a fourth argument (`useCapture`), a boolean that * indicated whether to use the browser's capturing phase or its bubbling phase. Since MSIE does @@ -572,7 +572,7 @@ * for that `eventName`. If `eventName` is also omitted, unregisters _all_ * event handlers on `element`. (In each case, only affects handlers registered via Prototype.) * - *

Examples

+ *
Examples
* * Assuming: * @@ -591,7 +591,7 @@ * * $('foo').stopObserving(); * - *

A Common Error

+ *
A Common Error
* * When using instance methods as observers, it's common to use [[Function#bind]] on them, e.g.: * diff --git a/src/dom/form.js b/src/dom/form.js index 37f4b139a..2c374e146 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -39,14 +39,14 @@ var Form = { * the value for that key in the object will be an array of the field values * in the order they appeared on the array of elements. * - *

The Options

+ *
The Options
* * The options allow you to control two things: What kind of return * value you get (an object or a string), and whether and which `submit` * fields are included in that object or string. * * If you do not supply an `options` object _at all_, the options - * `{hash: false}` are used. + * `{ hash: false }` are used. * * If you supply an `options` object, it may have the following options: * - `hash` ([[Boolean]]): `true` to return a plain object with keys and values @@ -64,7 +64,7 @@ var Form = { * _(Deprecated)_ If you pass in a [[Boolean]] instead of an object for `options`, it * is used as the `hash` option and all other options are defaulted. * - *

A hash, not a Hash

+ *
A hash, not a Hash
* * If you opt to receive an object, it is a plain JavaScript object with keys * and values, __not__ a [[Hash]]. All JavaScript objects are hashes in the lower-case @@ -629,7 +629,7 @@ Form.Element.EventObserver = Class.create(Abstract.EventObserver, { }); /** section: DOM - * class Form.Element.EventObserver < Abstract.EventObserver + * class Form.EventObserver < Abstract.EventObserver **/ Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { diff --git a/src/lang/class.js b/src/lang/class.js index bbf6eacaa..68b1fe649 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -86,7 +86,7 @@ var Class = (function() { * of the class and of all its subclasses, even those that have already been * instantiated. * - *

Examples

+ *
Examples
* * var Animal = Class.create({ * initialize: function(name, sound) { diff --git a/src/lang/string.js b/src/lang/string.js index 09fd46419..c0c70122c 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -127,11 +127,11 @@ Object.extend(String.prototype, (function() { * `span`, and `abbr`. It _will not_ strip namespace-prefixed tags such * as `h:table` or `xsl:template`. * - *

Caveat User

+ *
Caveat User
* * Note that the processing `stripTags` does is good enough for most purposes, but * you cannot rely on it for security purposes. If you're processing end-user-supplied - * content, `stripTags` is probably _not_ sufficiently robust to ensure that the content + * content, `stripTags` is _not_ sufficiently robust to ensure that the content * is completely devoid of HTML tags in the case of a user intentionally trying to circumvent * tag restrictions. But then, you'll be running them through [[String#escapeHTML]] anyway, * won't you? @@ -145,12 +145,12 @@ Object.extend(String.prototype, (function() { * * Strips a string of things that look like an HTML script blocks. * - *

Example

+ *
Example
* * "

This is a test.End of test

".stripScripts(); * // => "

This is a test.End of test

" * - *

Caveat User

+ *
Caveat User
* * Note that the processing `stripScripts` does is good enough for most purposes, * but you cannot rely on it for security purposes. If you're processing end-user-supplied @@ -183,7 +183,7 @@ Object.extend(String.prototype, (function() { * they were empty (the result for that position in the array will be `undefined`); * external files are _not_ loaded and processed by `evalScripts`. * - *

About `evalScripts`, `var`s, and defining functions

+ *
About `evalScripts`, `var`s, and defining functions
* * `evalScripts` evaluates script blocks, but this **does not** mean they are * evaluated in the global scope. They aren't, they're evaluated in the scope of @@ -302,7 +302,7 @@ Object.extend(String.prototype, (function() { * Converts a string separated by dashes into a camelCase equivalent. * For instance, 'foo-bar' would be converted to 'fooBar'. * - *

Examples

+ *
Examples
* * 'background-color'.camelize(); * // -> 'backgroundColor' From 19615e7a00ca31f35f07f25607a024bcc1ac9883 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 15 Sep 2009 18:27:35 -0500 Subject: [PATCH 002/502] Cleanup on PDoc templates. --- .../html/assets/javascripts/application.js | 620 ++++++++------ .../assets/javascripts/code_highlighter.js | 251 ------ .../html/assets/javascripts/prototype.js | 664 ++++++++------ templates/html/assets/stylesheets/api.css | 807 +++++++++++------- templates/html/assets/stylesheets/core.css | 415 --------- templates/html/assets/stylesheets/grid.css | 13 - .../html/assets/stylesheets/highlighter.css | 116 --- templates/html/assets/stylesheets/main.css | 443 ---------- templates/html/assets/stylesheets/screen.css | 244 ------ templates/html/helpers.rb | 15 +- templates/html/index.erb | 2 +- templates/html/layout.erb | 127 +-- templates/html/partials/short_description.erb | 4 +- 13 files changed, 1385 insertions(+), 2336 deletions(-) delete mode 100644 templates/html/assets/javascripts/code_highlighter.js delete mode 100644 templates/html/assets/stylesheets/core.css delete mode 100644 templates/html/assets/stylesheets/grid.css delete mode 100644 templates/html/assets/stylesheets/highlighter.css delete mode 100644 templates/html/assets/stylesheets/main.css delete mode 100644 templates/html/assets/stylesheets/screen.css diff --git a/templates/html/assets/javascripts/application.js b/templates/html/assets/javascripts/application.js index d6b720a3a..a6f503a9e 100644 --- a/templates/html/assets/javascripts/application.js +++ b/templates/html/assets/javascripts/application.js @@ -1,332 +1,439 @@ -if (typeof PDoc === "undefined") window.PDoc = {}; +//= require -// Poor-man's history manager. Polls for changes to the hash. -(function() { +if (!Prototype || Prototype.Version.indexOf('1.6') !== 0) { + throw "This script requires Prototype >= 1.6."; +} + +Object.isDate = function(object) { + return object instanceof Date; +}; + +/** + * class Cookie + * Creates a cookie. +**/ +var Cookie = Class.create({ + /** + * new Cookie(name, value[, expires]) + * + * - name (String): The name of the cookie. + * - value (String): The value of the cookie. + * - expires (Number | Date): Exact date (or number of days from now) that + * the cookie will expire. + **/ + initialize: function(name, value, expires) { + expires = expires || ""; + if (Object.isNumber(expires)) { + var days = expires; + expires = new Date(); + expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000)); + } + + if (Object.isDate(expires)) + expires = expires.toGMTString(); + + if (!Object.isUndefined(expires) && expires !== "") + expires = "; expires=" + expires; + + this.name = name; + this.value = value; + this.expires = expires; + + document.cookie = name + "=" + value + expires + "; path=/"; + }, + + toString: function() { + return this.value; + }, + + inspect: function() { + return "#".interpolate(this); + } +}); + +/** + * Cookie +**/ +Object.extend(Cookie, { + /** + * Cookie.set(name, value, expires) + * + * Alias of [[Cookie#initialize]]. + **/ + set: function(name, value, expires) { + return new Cookie(name, value, expires); + }, + + /** + * Cookie.get(name) + * + * Returns the value of the cookie with the given name. + * - name (String): The name of the cookie to retrieve. + **/ + get: function(name) { + var c = document.cookie.split(';'); + + for (var i = 0, cookie; i < c.length; i++) { + cookie = c[i].split('='); + if (cookie[0].strip() === name) + return cookie[1].strip(); + } + + return null; + }, + + /** + * Cookie.unset(name) + * + * Deletes a cookie. + * - name (String): The name of the cookie to delete. + * + **/ + unset: function(name) { + return Cookie.set(name, "", -1); + } +}); + +Cookie.erase = Cookie.unset; + + + +if (typeof PDoc === 'undefined') { + window.PDoc = { + Sidebar: {} + }; +} + +// HISTORY MANAGER (sort of) +// Polls for changes to the hash. + +(function() { var PREVIOUS_HASH = null; - Event.observe(window, "load", function() { + function poll() { var hash = window.location.hash; if (hash && hash !== PREVIOUS_HASH) { - document.fire("hash:changed", - { previous: PREVIOUS_HASH, current: hash }); - PREVIOUS_HASH = hash; + document.fire('hash:changed', { + previous: PREVIOUS_HASH, current: hash + }); } - - window.setTimeout(arguments.callee, 100); - }); + PREVIOUS_HASH = hash; + window.setTimeout(arguments.callee, 100); + } + + Event.observe(window, 'load', poll); })(); -// Place a "frame" around the element described by the hash. -// Update the frame when the hash changes. -PDoc.highlightSelected = function() { - if (!window.location.hash) return; - element = $(window.location.hash.substr(1)); - if (element) PDoc.highlight(element.up('li, div')); -}; +Object.extend(PDoc, { + highlightSelected: function() { + if (!window.location.hash) return; + var element = $(window.location.hash.substr(1)); + if (element) this.highlight(element.up('li, div')); + }, + + highlight: function(element) { + var self = arguments.callee; + if (!self.frame) { + self.frame = new Element('div', { 'class': 'highlighter' }); + document.body.appendChild(self.frame); + } + + var frame = self.frame; + element.getOffsetParent().appendChild(frame); + + var offset = element.positionedOffset(); + var w = parseFloat(element.getStyle('width')), + h = parseFloat(element.getStyle('height')); -document.observe("hash:changed", PDoc.highlightSelected); + frame.setStyle({ + position: 'absolute', + top: (offset.top - 15) + 'px', + left: (offset.left - 12) + 'px', + width: (w + 20) + 'px', + height: (h + 30) + 'px' + }); -PDoc.highlight = function(element) { - var self = arguments.callee; - if (!self.frame) { - self.frame = new Element('div', { 'class': 'highlighter' }); - document.body.appendChild(self.frame); + // Defer this call because Safari hasn't yet scrolled the viewport. + (function() { + var frameOffset = frame.viewportOffset(frame); + if (frameOffset.top < 0) { + $('page').scrollTop += (frameOffset.top - 10); + } + }).defer(); } +}); + +Object.extend(PDoc.Sidebar, { + getActiveTab: function() { + var activeTab = $('sidebar_tabs').down('.active'); + if (!activeTab) return null; + + var href = activeTab.readAttribute('href'); + return href.endsWith('menu_pane') ? 'menu_pane' : 'search_pane'; + }, - var frame = self.frame; - - element.getOffsetParent().appendChild(frame); - - var offset = element.positionedOffset(); - var w = parseFloat(element.getStyle('width')), - h = parseFloat(element.getStyle('height')); - - frame.setStyle({ - position: 'absolute', - top: (offset.top - 15) + 'px', - left: (offset.left - 12) + 'px', - width: (w + 20) + 'px', - height: (h + 30) + 'px' - }); - - // Defer this call because Safari hasn't yet scrolled the viewport. - (function() { - var frameOffset = frame.viewportOffset(frame); - if (frameOffset.top < 0) { - window.scrollBy(0, frameOffset.top - 10); - } - }).defer(); + // Remember the state of the sidebar so it can be restored on the next page. + serialize: function() { + var state = $H({ + activeTab: PDoc.Sidebar.getActiveTab(), + menuScrollOffset: $('menu_pane').scrollTop, + searchScrollOffset: $('search_results').scrollTop, + searchValue: $('search').getValue() + }); + + return escape(state.toJSON()); + }, -}; + // Restore the tree to a certain point based on a cookie. + restore: function(state) { + try { + state = unescape(state).evalJSON(); + var filterer = $('search').retrieve('filterer'); + filterer.setSearchValue(state.searchValue); + + (function() { + $('menu_pane').scrollTop = state.menuScrollOffset; + $('search_results').scrollTop = state.searchScrollOffset; + }).defer(); + } catch(error) { + console.log(error); + if (!(error instanceof SyntaxError)) throw error; + } + } +}); + + // Live API search. -var Filterer = Class.create({ +PDoc.Sidebar.Filterer = Class.create({ initialize: function(element, options) { this.element = $(element); - this.options = Object.extend({ - interval: 0.1, - resultsElement: '.search-results' - }, options || {}); + this.options = Object.extend( + Object.clone(PDoc.Sidebar.Filterer.DEFAULT_OPTIONS), + options || {} + ); - this.element.writeAttribute("autocomplete", "off"); - this.element.up('form').observe("submit", Event.stop); + // The browser's "helpful" auto-complete gets in the way. + this.element.writeAttribute("autocomplete", "off"); + this.element.setValue(''); - // // The Safari-only "search" input type is prettier - // if (Prototype.Browser.WebKit) - // this.element.type = "search"; + // Hitting "enter" should do nothing. + this.element.up('form').observe("submit", Event.stop); - this.menu = this.options.menu; + this.menu = this.options.menu; this.links = this.menu.select('a'); this.resultsElement = this.options.resultsElement; - this.resultsElement.setStyle({ - overflowX: 'hidden' - }); - this.events = { - filter: this.filter.bind(this), - keydown: this.keydown.bind(this) + this.observers = { + filter: this.filter.bind(this), + keydown: this.keydown.bind(this), + keyup: this.keyup.bind(this) }; this.menu.setStyle({ opacity: 0.9 }); - this.addObservers(); - - this.element.value = ''; + this.addObservers(); }, addObservers: function() { - this.element.observe('keyup', this.events.filter); + this.element.observe('keyup', this.observers.filter); }, - + + // Called whenever the list of results needs to update as a result of a + // changed search key. filter: function(event) { - if (this._timer) window.clearTimeout(this._timer); - - // Clear the text box on ESC + // Clear the text box on ESC. if (event.keyCode && event.keyCode === Event.KEY_ESC) { - this.element.value = ''; + this.element.setValue(''); } - if ([Event.KEY_UP, Event.KEY_DOWN, Event.KEY_RETURN].include(event.keyCode)) + if (PDoc.Sidebar.Filterer.INTERCEPT_KEYS.include(event.keyCode)) return; - + + // If there's nothing in the text box, clear the results list. var value = $F(this.element).strip().toLowerCase(); - if (value === "") { - this.onEmpty(); + if (value === '') { + this.emptyResults(); + this.hideResults(); return; } - var urls = this.findURLs(value); + var urls = this.findURLs(value); this.buildResults(urls); }, + setSearchValue: function(value) { + this.element.setValue(value); + if (value.strip() === "") { + PDoc.Sidebar.Tabs.setActiveTab(0); + return; + } + this.buildResults(this.findURLs(value)); + }, + + // Given a key, finds all the PDoc objects that match. + findURLs: function(str) { + var results = []; + for (var name in PDoc.elements) { + if (name.toLowerCase().include(str.toLowerCase())) + results.push(PDoc.elements[name]); + } + return results; + }, + + buildResults: function(results) { + this.emptyResults(); + + results.each( function(result) { + var li = this._buildResult(result); + this.resultsElement.appendChild(li); + }, this); + this.showResults(); + }, + + _buildResult: function(obj) { + var li = new Element('li', { 'class': 'menu-item' }); + var a = new Element('a', { + 'class': obj.type.gsub(/\s/, '_'), + 'href': PDoc.pathPrefix + this._fixPath(obj.path) + }).update(obj.name); + + li.appendChild(a); + return li; + }, + + emptyResults: function() { + this.resultsElement.update(); + }, + + hideResults: function() { + PDoc.Sidebar.Tabs.setActiveTab(0); + //this.resultsElement.hide(); + document.stopObserving('keydown', this.observers.keydown); + document.stopObserving('keyup', this.observers.keyup); + }, + + showResults: function() { + PDoc.Sidebar.Tabs.setActiveTab(1); + //this.resultsElement.show(); + document.stopObserving('keydown', this.observers.keydown); + this.element.stopObserving('keyup', this.observers.keyup); + this.element.observe('keydown', this.observers.keydown); + document.observe('keyup', this.observers.keyup); + }, + + // Given a path with any number of `../`s in front of it, remove them all. + // TODO: Fix this a better way. + _fixPath: function(path) { + return path.replace('../', ''); + }, + keydown: function(event) { - if (![Event.KEY_UP, Event.KEY_DOWN, Event.KEY_RETURN].include(event.keyCode)) + if (!PDoc.Sidebar.Filterer.INTERCEPT_KEYS.include(event.keyCode)) return; - // ignore if any modifier keys are present + // Also ignore if any modifier keys are present. if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return; event.stop(); - - var highlighted = this.resultsElement.down('.highlighted'); + if (event.keyCode === Event.KEY_RETURN) { - // Follow the highlighted item. - if (!highlighted) return; - window.location.href = highlighted.down('a').href; - } else { + // Follow the highlighted item, unless there is none. + if (!this.highlighted) return; + var a = this.highlighted.down('a'); + if (a) { + window.location.href = a.href; + } + } else if ([Event.KEY_UP, Event.KEY_DOWN].include(event.keyCode)) { + // Is an arrow key. var direction = (Event.KEY_DOWN === event.keyCode) ? 1 : -1; - highlighted = this.moveHighlight(direction); + this.highlighted = this.moveHighlight(direction); + + if (!Prototype.Browser.WebKit) { + // If up/down key is held down, list should keep scrolling. + // WebKit does this automatically because it fires the keydown + // event over and over. + this._scrollTimer = window.setTimeout( + this.scrollList.bind(this, direction), 1000); + } } - - - if ([Event.KEY_UP, Event.KEY_DOWN].include(event.keyCode) && - !Prototype.Browser.WebKit) { - // If up/down key is held down, list should keep scrolling. - // Safari does this automatically because it fires the keydown - // event over and over. - this._timer = window.setTimeout(this.scrollList.bind(this, direction), 1000); + }, + + keyup: function(event) { + if (this._scrollTimer) { + window.clearTimeout(this._scrollTimer); } }, moveHighlight: function(direction) { - var highlighted = this.resultsElement.down('.highlighted'); - // move the focus - if (!highlighted) { - // if there is none, highlight the first result - var highlighted = this.resultsElement.down('li').addClassName('highlighted'); + if (!this.highlighted) { + // If there is none, highlight the first result. + this.highlighted = + this.resultsElement.down('li').addClassName('highlighted'); } else { var method = (direction === 1) ? 'next' : 'previous'; - highlighted.removeClassName('highlighted'); - var adjacent = highlighted[method]('li'); + this.highlighted.removeClassName('highlighted'); + var adjacent = this.highlighted[method]('li'); + // If there isn't an adjacent one, we're at the top or bottom + // of the list. Flip it. if (!adjacent) { adjacent = method == 'next' ? this.resultsElement.down('li') : this.resultsElement.down('li:last-of-type'); } adjacent.addClassName('highlighted'); - highlighted = adjacent; - } - - // Adjust the scroll offset of the container so that the highlighted - // item is always in view. - var distanceToBottom = highlighted.offsetTop + highlighted.offsetHeight; - if (distanceToBottom > this.resultsElement.offsetHeight + this.resultsElement.scrollTop) { - // item is too low - this.resultsElement.scrollTop = distanceToBottom - this.resultsElement.offsetHeight; - } else if (highlighted.offsetTop < this.resultsElement.scrollTop) { - // item is too high - this.resultsElement.scrollTop = highlighted.offsetTop; + this.highlighted = adjacent; } - return highlighted; - }, - - scrollList: function(direction) { - this.moveHighlight(direction); - this._timer = window.setTimeout(this.scrollList.bind(this, direction), 100); - }, - - // Given a path with any number of `../`s in front of it, remove them all. - // TODO: Fix this a better way. - _fixPath: function(path) { - return path.replace('../', ''); - }, - - buildResults: function(urls) { - this.resultsElement.update(); - var ul = this.resultsElement; - urls.each( function(url) { - var a = new Element('a', { - 'class': url.type.gsub(/\s/, '_'), - href: PDoc.pathPrefix + this._fixPath(url.path) - }).update(url.name); - var li = new Element('li', { 'class': 'menu-item' }); - li.appendChild(a); - ul.appendChild(li); - }, this); - this.showResults(); - }, + var h = this.highlighted, r = this.resultsElement; - - findURLs: function(str) { - var results = []; - for (var i in PDoc.elements) { - if (i.toLowerCase().include(str)) results.push(PDoc.elements[i]); + var distanceToBottom = h.offsetTop + h.offsetHeight; + if (distanceToBottom > (r.offsetHeight + r.scrollTop)) { + // Item is below the visible frame. + r.scrollTop = distanceToBottom - r.offsetHeight; + } else if (h.offsetTop < r.scrollTop) { + // Item is above the visible frame. + r.scrollTop = h.offsetTop; } - return results; - }, - - onEmpty: function() { - this.hideResults(); - }, - - showResults: function() { - this.resultsElement.show(); - document.stopObserving("keydown", this.events.keydown); - document.observe("keydown", this.events.keydown); + + return this.highlighted; }, - hideResults: function() { - this.resultsElement.hide(); - document.stopObserving("keydown", this.events.keydown); - } -}); - -document.observe('dom:loaded', function() { - new Filterer($('search'), { - menu: $('api_menu'), - resultsElement: $('search_results') - }); + scrollList: function(direction) { + this.moveHighlight(direction); + this._scrollTimer = window.setTimeout( + this.scrollList.bind(this, direction), 100); + } }); - -Event.observe(window, 'load', function() { - var menu = $('menu'); - var OFFSET = menu.viewportOffset().top; - - Event.observe(window, 'scroll', function() { - var sOffset = document.viewport.getScrollOffsets(); - if (sOffset.top > OFFSET) { - menu.addClassName('fixed'); - } else menu.removeClassName('fixed'); - }); +Object.extend(PDoc.Sidebar.Filterer, { + INTERCEPT_KEYS: [Event.KEY_UP, Event.KEY_DOWN, Event.KEY_RETURN], + DEFAULT_OPTIONS: { + interval: 0.1, + resultsElement: '.search-results' + } }); -(function() { - function menuButtonMouseOver(event) { - var menuButton = $('api_menu_button'); - var target = event.element(); - if (target === menuButton || target.descendantOf(menuButton)) { - $('api_menu').show(); - } - } - - function menuButtonMouseOut(event) { - var menuButton = $('api_menu_button'); - var menu = $('api_menu'); - var target = event.element(), related = event.relatedTarget || event.toElement; - - if (related && (related === menu || related.descendantOf(menu))) return; - menu.hide(); - } - - function menuMouseOut(event) { - var menu = $('api_menu'), related = event.relatedTarget || event.toElement; - if (related && !related.descendantOf(menu)) { - arguments.callee.timer = Element.hide.delay(0.5, menu); - } else { - window.clearTimeout(arguments.callee.timer); - } - } - - function menuItemMouseOver(event) { - var element = event.element(); - if (element.tagName.toLowerCase() === 'a') { - element.addClassName('highlighted'); - } - } - - function menuItemMouseOut(event) { - var element = event.element(); - if (element.tagName.toLowerCase() === 'a') { - element.removeClassName('highlighted'); - } - } - - var MENU_ITEMS; - - document.observe('dom:loaded', function() { - MENU_ITEMS = $$('.api-box .menu-item a'); - - $('api_menu_button').observe('mouseenter', menuButtonMouseOver); - $('api_menu_button').observe('mouseleave', menuButtonMouseOut ); - - $('api_menu').observe('mouseleave', menuMouseOut); - - if (Prototype.Browser.IE) { - $('api_menu').observe('mouseover', menuItemMouseOver); - $('api_menu').observe('mouseout', menuItemMouseOut); - } - }); -})(); Form.GhostedField = Class.create({ initialize: function(element, title, options) { + options = options || {}; + this.element = $(element); this.title = title; - options = options || {}; - this.isGhosted = true; if (options.cloak) { + // Wrap the native getValue function so that it never returns the // ghosted value. This is optional because it presumes the ghosted // value isn't valid input for the field. this.element.getValue = this.element.getValue.wrap(this.wrappedGetValue.bind(this)); - } + } this.addObservers(); + this.onBlur(); }, @@ -384,6 +491,35 @@ Form.GhostedField = Class.create({ } }); -document.observe("dom:loaded", function() { - new Form.GhostedField($('search'), "Search"); + +document.observe('hash:changed', PDoc.highlightSelected.bind(PDoc)); +document.observe('dom:loaded', function() { + PDoc.Sidebar.Tabs = new Control.Tabs($('sidebar_tabs')); + + var searchField = $('search'); + + if (searchField) { + var filterer = new PDoc.Sidebar.Filterer(searchField, { + menu: $('api_menu'), + resultsElement: $('search_results') + }); + searchField.store('filterer', filterer); + } + + // Prevent horizontal scrolling in scrollable sidebar areas. + $$('.scrollable').invoke('observe', 'scroll', function() { + this.scrollLeft = 0; + }); + + var sidebarState = Cookie.get('sidebar_state'); + if (sidebarState) { + PDoc.Sidebar.restore(sidebarState); + } + + new Form.GhostedField(searchField, searchField.getAttribute('title'), + { cloak: true }); +}); + +Event.observe(window, 'unload', function() { + Cookie.set('sidebar_state', PDoc.Sidebar.serialize()); }); \ No newline at end of file diff --git a/templates/html/assets/javascripts/code_highlighter.js b/templates/html/assets/javascripts/code_highlighter.js deleted file mode 100644 index 4caa49c3a..000000000 --- a/templates/html/assets/javascripts/code_highlighter.js +++ /dev/null @@ -1,251 +0,0 @@ -/* Unobtrustive Code Highlighter By Dan Webb 11/2005 - Version: 0.4 - - Usage: - Add a script tag for this script and any stylesets you need to use - to the page in question, add correct class names to CODE elements, - define CSS styles for elements. That's it! - - Known to work on: - IE 5.5+ PC - Firefox/Mozilla PC/Mac - Opera 7.23 + PC - Safari 2 - - Known to degrade gracefully on: - IE5.0 PC - - Note: IE5.0 fails due to the use of lookahead in some stylesets. To avoid script errors - in older browsers use expressions that use lookahead in string format when defining stylesets. - - This script is inspired by star-light by entirely cunning Dean Edwards - http://dean.edwards.name/star-light/. -*/ - -// replace callback support for safari. -if ("a".replace(/a/, function() {return "b"}) != "b") (function(){ - var default_replace = String.prototype.replace; - String.prototype.replace = function(search,replace){ - // replace is not function - if(typeof replace != "function"){ - return default_replace.apply(this,arguments) - } - var str = "" + this; - var callback = replace; - // search string is not RegExp - if(!(search instanceof RegExp)){ - var idx = str.indexOf(search); - return ( - idx == -1 ? str : - default_replace.apply(str,[search,callback(search, idx, str)]) - ) - } - var reg = search; - var result = []; - var lastidx = reg.lastIndex; - var re; - while((re = reg.exec(str)) != null){ - var idx = re.index; - var args = re.concat(idx, str); - result.push( - str.slice(lastidx,idx), - callback.apply(null,args).toString() - ); - if(!reg.global){ - lastidx += RegExp.lastMatch.length; - break - }else{ - lastidx = reg.lastIndex; - } - } - result.push(str.slice(lastidx)); - return result.join("") - } -})(); - -var CodeHighlighter = { styleSets : new Array }; - -CodeHighlighter.addStyle = function(name, rules) { - // using push test to disallow older browsers from adding styleSets - if ([].push) this.styleSets.push({ - name : name, - rules : rules, - ignoreCase : arguments[2] || false - }) - - function setEvent() { - // set highlighter to run on load (use LowPro if present) - if (typeof Event != 'undefined' && typeof Event.onReady == 'function') - return Event.onReady(CodeHighlighter.init.bind(CodeHighlighter)); - - var old = window.onload; - - if (typeof window.onload != 'function') { - window.onload = function() { CodeHighlighter.init() }; - } else { - window.onload = function() { - old(); - CodeHighlighter.init(); - } - } - } - - // only set the event when the first style is added - if (this.styleSets.length==1) setEvent(); -} - -CodeHighlighter.init = function() { - if (!document.getElementsByTagName) return; - if ("a".replace(/a/, function() {return "b"}) != "b") return; // throw out Safari versions that don't support replace function - // throw out older browsers - - var codeEls = document.getElementsByTagName("CODE"); - // collect array of all pre elements - codeEls.filter = function(f) { - var a = new Array; - for (var i = 0; i < this.length; i++) if (f(this[i])) a[a.length] = this[i]; - return a; - } - - var rules = new Array; - rules.toString = function() { - // joins regexes into one big parallel regex - var exps = new Array; - for (var i = 0; i < this.length; i++) exps.push(this[i].exp); - return exps.join("|"); - } - - function addRule(className, rule) { - // add a replace rule - var exp = (typeof rule.exp != "string")?String(rule.exp).substr(1, String(rule.exp).length-2):rule.exp; - // converts regex rules to strings and chops of the slashes - rules.push({ - className : className, - exp : "(" + exp + ")", - length : (exp.match(/(^|[^\\])\([^?]/g) || "").length + 1, // number of subexps in rule - replacement : rule.replacement || null - }); - } - - function parse(text, ignoreCase) { - // main text parsing and replacement - return text.replace(new RegExp(rules, (ignoreCase)?"gi":"g"), function() { - var i = 0, j = 1, rule; - while (rule = rules[i++]) { - if (arguments[j]) { - // if no custom replacement defined do the simple replacement - if (!rule.replacement) return "" + arguments[0] + ""; - else { - // replace $0 with the className then do normal replaces - var str = rule.replacement.replace("$0", rule.className); - for (var k = 1; k <= rule.length - 1; k++) str = str.replace("$" + k, arguments[j + k]); - return str; - } - } else j+= rule.length; - } - }); - } - - function highlightCode(styleSet) { - // clear rules array - var parsed; - rules.length = 0; - - // get stylable elements by filtering out all code elements without the correct className - var stylableEls = codeEls.filter(function(item) {return (item.className.indexOf(styleSet.name)>=0)}); - - // add style rules to parser - for (var className in styleSet.rules) addRule(className, styleSet.rules[className]); - - - // replace for all elements - for (var i = 0; i < stylableEls.length; i++) { - // EVIL hack to fix IE whitespace badness if it's inside a
-			if (/MSIE/.test(navigator.appVersion) && stylableEls[i].parentNode.nodeName == 'PRE') {
-				stylableEls[i] = stylableEls[i].parentNode;
-				
-				parsed = stylableEls[i].innerHTML.replace(/(]*>)([^<]*)<\/code>/i, function() {
-					return arguments[1] + parse(arguments[2], styleSet.ignoreCase) + ""
-				});
-				parsed = parsed.replace(/\n( *)/g, function() { 
-					var spaces = "";
-					for (var i = 0; i < arguments[1].length; i++) spaces+= " ";
-					return "\n" + spaces;  
-				});
-				parsed = parsed.replace(/\t/g, "    ");
-				parsed = parsed.replace(/\n(<\/\w+>)?/g, "
$1").replace(/
[\n\r\s]*
/g, "


"); - - } else parsed = parse(stylableEls[i].innerHTML, styleSet.ignoreCase); - - stylableEls[i].innerHTML = parsed; - } - } - - // run highlighter on all stylesets - for (var i=0; i < this.styleSets.length; i++) { - highlightCode(this.styleSets[i]); - } -}; - -CodeHighlighter.addStyle("css", { - comment : { - exp : /\/\*[^*]*\*+([^\/][^*]*\*+)*\// - }, - keywords : { - exp : /@\w[\w\s]*/ - }, - selectors : { - exp : "([\\w-:\\[.#][^{};>]*)(?={)" - }, - properties : { - exp : "([\\w-]+)(?=\\s*:)" - }, - units : { - exp : /([0-9])(em|en|px|%|pt)\b/, - replacement : "$1$2" - }, - urls : { - exp : /url\([^\)]*\)/ - } -}); - -CodeHighlighter.addStyle("html", { - comment : { - exp: /<!\s*(--([^-]|[\r\n]|-[^-])*--\s*)>/ - }, - tag : { - exp: /(<\/?)([a-zA-Z]+\s?)/, - replacement: "$1$2" - }, - string : { - exp : /'[^']*'|"[^"]*"/ - }, - attribute : { - exp: /\b([a-zA-Z-:]+)(=)/, - replacement: "$1$2" - }, - doctype : { - exp: /<!DOCTYPE([^&]|&[^g]|&g[^t])*>/ - } -}); - -CodeHighlighter.addStyle("javascript",{ - comment : { - exp : /(\/\/[^\n]*(\n|$))|(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)/ - }, - brackets : { - exp : /\(|\)/ - }, - regex : { - exp : /\/(.*?)[g|s|m]?\/[;|\n]/ - }, - string : { - exp : /'(?:\.|(\\\')|[^\''])*'|"(?:\.|(\\\")|[^\""])*"/ - }, - keywords : { - exp : /\b(arguments|break|case|continue|default|delete|do|else|false|for|function|if|in|instanceof|new|null|return|switch|this|true|typeof|var|void|while|with)\b/ - }, - global : { - exp : /\b(toString|valueOf|window|element|prototype|constructor|document|escape|unescape|parseInt|parseFloat|setTimeout|clearTimeout|setInterval|clearInterval|NaN|isNaN|Infinity|alert|prompt|confirm)\b/ - } -}); diff --git a/templates/html/assets/javascripts/prototype.js b/templates/html/assets/javascripts/prototype.js index a0114e0c2..9fe6e1243 100644 --- a/templates/html/assets/javascripts/prototype.js +++ b/templates/html/assets/javascripts/prototype.js @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.6.0.3 +/* Prototype JavaScript framework, version 1.6.1 * (c) 2005-2009 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. @@ -7,38 +7,42 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.4_rc0', - - Browser: { - IE: !!(window.attachEvent && - navigator.userAgent.indexOf('Opera') === -1), - Opera: navigator.userAgent.indexOf('Opera') > -1, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && - navigator.userAgent.indexOf('KHTML') === -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, + Version: '1.6.1', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + } + })(), BrowserFeatures: { XPath: !!document.evaluate, SelectorsAPI: !!document.querySelector, ElementExtensions: (function() { - if (window.HTMLElement && window.HTMLElement.prototype) - return true; - if (window.Element && window.Element.prototype) - return true; + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); })(), SpecificElementExtensions: (function() { if (typeof window.HTMLDivElement !== 'undefined') return true; var div = document.createElement('div'); - if (div['__proto__'] && div['__proto__'] !== - document.createElement('form')['__proto__']) { - return true; + var form = document.createElement('form'); + var isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; } - return false; + div = form = null; + + return isSupported; })() }, @@ -75,6 +79,7 @@ var Try = { /* Based on Alex Arnell's inheritance implementation. */ var Class = (function() { + function subclass() {}; function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) @@ -89,7 +94,6 @@ var Class = (function() { klass.subclasses = []; if (parent) { - var subclass = function() {}; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); @@ -143,10 +147,7 @@ var Class = (function() { })(); (function() { - function getClass(object) { - return Object.prototype.toString.call(object) - .match(/^\[object\s(.*)\]$/)[1]; - } + var _toString = Object.prototype.toString; function extend(destination, source) { for (var property in source) @@ -219,7 +220,7 @@ var Class = (function() { } function isArray(object) { - return getClass(object) === "Array"; + return _toString.call(object) == "[object Array]"; } @@ -232,11 +233,11 @@ var Class = (function() { } function isString(object) { - return getClass(object) === "String"; + return _toString.call(object) == "[object String]"; } function isNumber(object) { - return getClass(object) === "Number"; + return _toString.call(object) == "[object Number]"; } function isUndefined(object) { @@ -394,8 +395,10 @@ var PeriodicalExecuter = Class.create({ try { this.currentlyExecuting = true; this.execute(); - } finally { this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; } } } @@ -473,7 +476,7 @@ Object.extend(String.prototype, (function() { } function stripTags() { - return this.replace(/<\/?[^>]+>/gi, ''); + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); } function stripScripts() { @@ -493,18 +496,14 @@ Object.extend(String.prototype, (function() { } function escapeHTML() { - escapeHTML.text.data = this; - return escapeHTML.div.innerHTML; + return this.replace(/&/g,'&').replace(//g,'>'); } function unescapeHTML() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); } + function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; @@ -557,17 +556,23 @@ Object.extend(String.prototype, (function() { } function underscore() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); } function dasherize() { - return this.gsub(/_/,'-'); + return this.replace(/_/g, '-'); } function inspect(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; @@ -578,7 +583,7 @@ Object.extend(String.prototype, (function() { } function unfilterJSON(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); + return this.replace(filter || Prototype.JSONFilter, '$1'); } function isJSON() { @@ -626,7 +631,7 @@ Object.extend(String.prototype, (function() { sub: sub, scan: scan, truncate: truncate, - strip: strip, + strip: String.prototype.trim ? String.prototype.trim : strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, @@ -656,22 +661,6 @@ Object.extend(String.prototype, (function() { }; })()); -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); - } -}); - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); - -String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); - var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); @@ -679,11 +668,11 @@ var Template = Class.create({ }, evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) + if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; + if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; @@ -694,7 +683,7 @@ var Template = Class.create({ if (match == null) return before; while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); @@ -913,6 +902,14 @@ var Enumerable = (function() { return '#'; } + + + + + + + + return { each: each, eachSlice: eachSlice, @@ -948,24 +945,12 @@ var Enumerable = (function() { })(); function $A(iterable) { if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); + if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } -if (Prototype.Browser.WebKit) { - $A = function(iterable) { - if (!iterable) return []; - if (!(typeof iterable === 'function' && typeof iterable.length === - 'number' && typeof iterable.item === 'function') && iterable.toArray) - return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - }; -} - function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); @@ -974,6 +959,7 @@ function $w(string) { Array.from = $A; + (function() { var arrayProto = Array.prototype, slice = arrayProto.slice, @@ -1024,10 +1010,6 @@ Array.from = $A; return (inline !== false ? this : this.toArray())._reverse(); } - function reduce() { - return this.length > 1 ? this : this[0]; - } - function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) @@ -1042,6 +1024,7 @@ Array.from = $A; }); } + function clone() { return slice.call(this, 0); } @@ -1106,7 +1089,6 @@ Array.from = $A; flatten: flatten, without: without, reverse: reverse, - reduce: reduce, uniq: uniq, intersect: intersect, clone: clone, @@ -1326,6 +1308,8 @@ var ObjectRange = Class.create(Enumerable, (function() { }; })()); + + var Ajax = { getTransport: function() { return Try.these( @@ -1337,6 +1321,7 @@ var Ajax = { activeRequestCount: 0 }; + Ajax.Responders = { responders: [], @@ -1540,7 +1525,7 @@ Ajax.Request = Class.create(Ajax.Base, { getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } + } catch (e) { return null; } }, evalResponse: function() { @@ -1559,6 +1544,14 @@ Ajax.Request = Class.create(Ajax.Base, { Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + + Ajax.Response = Class.create({ initialize: function(request){ this.request = request; @@ -1580,6 +1573,7 @@ Ajax.Response = Class.create({ }, status: 0, + statusText: '', getStatus: Ajax.Request.prototype.getStatus, @@ -1632,6 +1626,7 @@ Ajax.Response = Class.create({ } } }); + Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { @@ -1667,6 +1662,7 @@ Ajax.Updater = Class.create(Ajax.Request, { } } }); + Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); @@ -1755,12 +1751,28 @@ if (!Node.ELEMENT_NODE) { (function(global) { + + var SETATTRIBUTE_IGNORES_NAME = (function(){ + var elForm = document.createElement("form"); + var elInput = document.createElement("input"); + var root = document.documentElement; + elInput.setAttribute("name", "test"); + elForm.appendChild(elInput); + root.appendChild(elForm); + var isBuggy = elForm.elements + ? (typeof elForm.elements.test == "undefined") + : null; + root.removeChild(elForm); + elForm = elInput = null; + return isBuggy; + })(); + var element = global.Element; global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { + if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); @@ -1805,15 +1817,89 @@ Element.Methods = { return element; }, - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), replace: function(element, content) { element = $(element); @@ -1898,7 +1984,7 @@ Element.Methods = { }, ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); + return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { @@ -1919,16 +2005,17 @@ Element.Methods = { }, previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); + return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); + return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); }, match: function(element, selector) { @@ -1940,22 +2027,22 @@ Element.Methods = { up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); + var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? element.descendants()[expression] : + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); + var previousSiblings = Element.previousSiblings(element); return Object.isNumber(expression) ? previousSiblings[expression] : Selector.findElement(previousSiblings, expression, index); }, @@ -1963,67 +2050,44 @@ Element.Methods = { next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); + var nextSiblings = Element.nextSiblings(element); return Object.isNumber(expression) ? nextSiblings[expression] : Selector.findElement(nextSiblings, expression, index); }, - select: function() { - var args = $A(arguments), element = $(args.shift()); + select: function(element) { + var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element, args); }, - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); + adjacent: function(element) { + var args = Array.prototype.slice.call(arguments, 1); return Selector.findChildElements(element.parentNode, args).without(element); }, identify: function(element) { element = $(element); - var id = element.readAttribute('id'); + var id = Element.readAttribute(element, 'id'); if (id) return id; do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); - element.writeAttribute('id', id); + Element.writeAttribute(element, 'id', id); return id; }, - readAttribute: (function(){ - - var iframeGetAttributeThrowsError = (function(){ - var el = document.createElement('iframe'), - isBuggy = false; - - document.documentElement.appendChild(el); - try { - el.getAttribute('type', 2); - } catch(e) { - isBuggy = true; - } - document.documentElement.removeChild(el); - el = null; - return isBuggy; - })(); - - return function(element, name) { - element = $(element); - if (iframeGetAttributeThrowsError && - name === 'type' && - element.tagName.toUpperCase() == 'IFRAME') { - return element.getAttribute('type'); - } - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; } - return element.getAttribute(name); } - })(), + return element.getAttribute(name); + }, writeAttribute: function(element, name, value) { element = $(element); @@ -2046,11 +2110,11 @@ Element.Methods = { }, getHeight: function(element) { - return $(element).getDimensions().height; + return Element.getDimensions(element).height; }, getWidth: function(element) { - return $(element).getDimensions().width; + return Element.getDimensions(element).width; }, classNames: function(element) { @@ -2066,7 +2130,7 @@ Element.Methods = { addClassName: function(element, className) { if (!(element = $(element))) return; - if (!element.hasClassName(className)) + if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, @@ -2080,8 +2144,8 @@ Element.Methods = { toggleClassName: function(element, className) { if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); }, cleanWhitespace: function(element) { @@ -2117,7 +2181,7 @@ Element.Methods = { scrollTo: function(element) { element = $(element); - var pos = element.cumulativeOffset(); + var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, @@ -2165,7 +2229,7 @@ Element.Methods = { getDimensions: function(element) { element = $(element); - var display = element.getStyle('display'); + var display = Element.getStyle(element, 'display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight}; @@ -2256,9 +2320,9 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return element; + if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = element.positionedOffset(); + var offsets = Element.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; @@ -2279,7 +2343,7 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return element; + if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); @@ -2348,14 +2412,14 @@ Element.Methods = { }, arguments[2] || { }); source = $(source); - var p = source.viewportOffset(); + var p = Element.viewportOffset(source); element = $(element); var delta = [0, 0]; var parent = null; if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); } if (parent == document.body) { @@ -2510,41 +2574,92 @@ else if (Prototype.Browser.IE) { return element; }; - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - - if (!attribute) return null; - attribute = attribute.toString(); - attribute = attribute.split('{')[1]; - attribute = attribute.split('}')[0]; - return attribute.strip(); - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); + Element._attributeTranslations = (function(){ + + var classProp = 'className'; + var forProp = 'for'; + + var el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp }, - title: function(element) { - return element.title; + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'); + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + var f; + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } } } } - }; + })(); Element._attributeTranslations.write = { names: Object.extend({ @@ -2572,8 +2687,8 @@ else if (Prototype.Browser.IE) { (function(v) { Object.extend(v, { - href: v._getAttr, - src: v._getAttr, + href: v._getAttr2, + src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, @@ -2664,29 +2779,7 @@ else if (Prototype.Browser.WebKit) { }; } -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -if ('outerHTML' in document.createElement('div')) { +if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2754,12 +2847,13 @@ Element._insertionTranslations = { }; (function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD }); -}).call(Element._insertionTranslations); +})(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { @@ -2786,8 +2880,49 @@ Object.extend(Element, Element.Methods); })(document.createElement('div')) Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2); + var el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } return Prototype.K; + } var Methods = { }, ByTag = Element.Methods.ByTag; @@ -2796,15 +2931,11 @@ Element.extend = (function() { element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; + tagName = element.tagName.toUpperCase(); if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } + extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; @@ -2987,9 +3118,9 @@ Element.addMethods({ if (!(element = $(element))) return; if (arguments.length === 2) { - element.getStorage().update(key); + Element.getStorage(element).update(key); } else { - element.getStorage().set(key, value); + Element.getStorage(element).set(key, value); } return element; @@ -3005,6 +3136,20 @@ Element.addMethods({ } return value; + }, + + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); } }); /* Portions of the Selector class are derived from Jack Slocum's DomQuery, @@ -3147,7 +3292,7 @@ var Selector = Class.create({ case 'selectorsAPI': if (root !== document) { var oldId = root.id, id = $(root).identify(); - id = id.replace(/[\.:]/g, "\\$0"); + id = id.replace(/([\.:])/g, "\\$1"); e = "#" + id + " " + e; } @@ -3395,11 +3540,31 @@ Object.extend(Selector, { return nodes; }, - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, + unmark: (function(){ + + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x' + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), index: function(parentNode, reverse, ofType) { parentNode._countedByPrototype = Prototype.emptyFunction; @@ -3741,12 +3906,6 @@ if (Prototype.Browser.IE) { for (var i = 0, node; node = b[i]; i++) if (node.tagName !== "!") a.push(node); return a; - }, - - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; } }); } @@ -3792,13 +3951,18 @@ Form.Methods = { }, getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) }, getInputs: function(form, typeName, name) { @@ -3838,7 +4002,7 @@ Form.Methods = { }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, @@ -3924,7 +4088,7 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; @@ -4118,6 +4282,10 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { cache: {} }; + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + var _isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; @@ -4270,7 +4438,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { } var respondersForEvent = registry.get(eventName); - if (Object.isUndefined()) { + if (Object.isUndefined(respondersForEvent)) { respondersForEvent = []; registry.set(eventName, respondersForEvent); } @@ -4290,7 +4458,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { handler.call(element, event); }; } else { - if (!Prototype.Browser.IE && + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === "mouseenter" || eventName === "mouseleave")) { if (eventName === "mouseenter" || eventName === "mouseleave") { responder = function(event) { @@ -4338,7 +4506,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { var _getDOMEventName = Prototype.K; - if (!Prototype.Browser.IE) { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; return eventName in translations ? translations[eventName] : eventName; diff --git a/templates/html/assets/stylesheets/api.css b/templates/html/assets/stylesheets/api.css index 41c4278d7..c0b304c30 100644 --- a/templates/html/assets/stylesheets/api.css +++ b/templates/html/assets/stylesheets/api.css @@ -1,214 +1,373 @@ -/* - * API STYLES - */ +/* The "section" class implicitly needs a clearfix; adding it here for convenience. */ +.clearfix:after, +.section:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} -/* tag styles */ -pre { +.clearfix, .section { + display: inline-block; +} + +html[xmlns] .clearfix, +html[xmlns] .section { + display: block; +} + +* html .clearfix, +* html .section { + height: 1%; +} + +span.replaced { visibility: hidden; } +span.hidden { display: none; } + + +body { + font-family: Verdana, sans-serif; + font-size: 82.5%; + line-height: 1.5em; + margin: 0; padding: 0; } + body * { + margin: 0; + padding: 0; + } + +h1, h2, h3, h4, h5, h6 { + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; +} + +pre { + white-space: pre-wrap; /* CSS 3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ +} + +a img { + border: 0; +} + +ul, li { + list-style-type: none; +} + +ol, ul { + margin: 0 0 15px; +} + code { - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", monospace; + font-family: "Panic Sans", "Bitstream Vera Sans Mono", Monaco, Consolas, Andale Mono, monospace; font-size: 12px; } -/* masthead */ -div#masthead { - background: url(../images/header-stripe-small.png) repeat-x top left; - height: 76px; +#page a.img, +#page a.img:link, +#page a.img:visited { + border: 0; +} + +.content { + padding-left: 120px; + margin: 0 auto; } - div#masthead div#masthead_content { - margin: 0 auto; - width: 835px; - position: relative; +#page { + margin: 0; + position: fixed; + top: 0; + bottom: 0; + right: 0; + left: 241px; + overflow-y: scroll; + overflow-x: auto; +} + + +/* MASTHEAD */ + +.masthead { + margin-top: 50px; + height: 75px; + padding: 1px 0; + background: url(../images/header-stripe-small.png) repeat-x; +} + +h1.logo { + width: 236px; + height: 150px; + background: url(../images/header-logo-small.png) no-repeat; +} + + h1.logo a { + text-decoration: none; } + +/* BREADCRUMBS */ + +ul.breadcrumbs { + margin-left: 120px; + padding-left: 120px; + list-style-type: none; +} - div#masthead h1#logo { - background: url(../images/header-logo-small.png) no-repeat 0 1px; - width: 118px; - height: 76px; - position: relative; - left: 60px; + ul.breadcrumbs li { + float: left; + list-style-type: none; + margin-right: 10px; + margin-left: 0; } -/* footer */ -div#footer { - width: 960px; - margin: 0 auto; + +/* PAGE CONTENT */ + +.page-content { + width: 715px; + margin: 30px 0 0; } - div.about { - margin-top: 20px; - padding-top: 20px; - width: 835px; + .page-content h2.page-title { + margin: 0 0 15px 120px; + line-height: 100%; + font-size: 27px; + letter-spacing: -1px; + color: #444; + } + + .page-introduction { margin-left: 120px; - border-top: 1px solid #aaa; - color: #aaa; + margin-bottom: 25px; + } + + .page-content a, + .page-content a:link { + color: #036; + border-bottom: 1px solid #036; + text-decoration: none; + } + + .page-content a:visited { + border-bottom: 1px solid #bbb; + } + + .page-content ul, + .page-content ol { + margin: 10px 0; + } + + .page-content li { + margin: 5px 0 8px 20px; + list-style-type: disc; + } + + .page-content p { + margin: 0 0 0.8em; + } + + .page-content code { + background-color: #f0f0f0; + border: 1px solid #ccc; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + padding: 0 3px; } - div.about a, - div.about a:link { - color: #aaa; - text-decoration: underline; + .page-content pre { + color: #333; + background-color: #f0f0ff; + border: 1px solid #dde; + padding: 3px 5px; + margin: 0 0 1em; + } + + .page-content pre code { + background-color: transparent; border: 0; + padding: 0; + line-height: 100%; } + + + .page-content code { - div.copyright, - div.credits { - width: 360px; - float: left; } - div.copyright .cc { - width: 115px; - margin-right: 5px; - text-align: center; - float: left; - } + .page-content h1 { + font-size: 27px; + color: #000; + margin: 1.0em 0 0.6em; + } - div.copyright .copyright-about { - width: 235px; - float: left; + .page-content h2 { + font-size: 23px; + color: #000; + margin: 1.0em 0 0.6em; + } + + .page-content h3 { + font-size: 20px; + color: #000; + margin: 1.0em 0 0.6em; + } + + .page-content h4 { + font-size: 17px; + color: #555; + margin: 1.0em 0 0.6em; + } + + .page-content h5 { + font-size: 15px; + color: #2a2a2a; + margin: 1.0em 0 0.6em; + } + + .page-content h6 { + font-size: 14px; + color: #000; + margin: 1.0em 0 0.6em; + } + +/* PAGE SECTIONS */ + +.section { + margin: 10px 0 15px; +} + + .section-title { + width: 110px; + float: left; + margin-right: 10px; + padding-right: 0; + text-align: right; + overflow: hidden; + } + + .section-title h3 { + color: #999; + font-size: 14px; + line-height: 110%; + margin-top: 3px; + padding-right: 10px; + padding-bottom: 5px; } - div.credits { - margin-left: 115px; + .section-content { + width: 595px; + float: left; } -.page-title span.type { +/* API STYLES */ + +.page-title .type { display: block; text-transform: uppercase; font-size: 14px; color: #aaa; - letter-spacing: 0; } -h2.page-title { - margin-top: 0; - line-height: 100%; -} -ul.breadcrumbs { - margin-left: 120px; +ul.section-list { + list-style-type: none; + margin-top: 0; } - ul.breadcrumbs li { - float: left; + ul.section-list li { list-style-type: none; - margin-right: 10px; - margin-left: 0; + margin: 0 0 15px; } - - - + + ul.section-list li h4 { + margin-top: 0; + } + ul.method-list { margin-top: 0; } - + ul.method-list li { - margin-top: 0; float: left; - margin-right: 5px; - margin-left: 0; + margin: 0 5px 3px 0; list-style-type: none; + padding-bottom: 0; } - + ul.method-list li a, ul.method-list li a:link { text-decoration: none; border: 0; } - + ul.method-details-list { margin-top: 0; } - - ul.method-details-list li.method-description { - margin-top: 0; - margin-bottom: 3.0em; - margin-left: 0; + + li.method-description { + margin: 0 0 2.0em; list-style-type: none; } - - ul.method-details-list li h4 { + + .method-description h4 { margin: 0 0 0.6em; line-height: 90%; } - - ul.method-details-list li pre { - margin-top: 0; - } - - ul.method-details-list li pre code { - font-size: 13px; - } - ul.method-details-list li code { - font-size: 12px; + .method-description p { + margin: 0.8em 0; } - - h4.inherited { - padding-top: 5px; - clear: left; - font-style: italic; - font-weight: bold; - font-size: 14px; - } - -.method-description h4 .method-permalink a { + + + .method-description .method-permalink a { color: #aaa; text-decoration: none; - border: 0; + border-bottom: 0; font-size: 14px; } +h4.inherited { + clear: left; + font-size: 15px; + font-style: italic; +} + +pre.syntax { + margin-bottom: 5px; +} + ul.argument-list { - margin-top: -5px; - list-style-type: disc; - margin-left: 20px; + font-size: 12px; + padding: 0; + margin: 0; } ul.argument-list li { - list-style-type: disc; - font-size: 90%; - margin-bottom: 0; + line-height: 140%; + margin-top: 0px; + margin-bottom: 5px; } ul.argument-list li code { font-size: 11px; } -ul.section-list { - margin-top: 0; -} - - ul.section-list li { - margin-top: 0; - margin-left: 0; - list-style-type: none; - } - - ul.section-list li h4 { - margin-top: 0; + ul.argument-list .argument-name { + background-color: #eeffee; + border-color: #6b6; } - - -/* Aliases */ - -.alias, -.related-to { - font-style: italic; -} - - .alias code, - .related-to code { - font-style: normal; - } -/* Section icons */ + +/* SECTION ICONS */ .page-content .section .section-title h3 { padding-right: 24px; @@ -272,88 +431,37 @@ ul.section-list { background-image: url(../images/superclass.png); } -/* search box */ - -.search-results { - position: absolute; - background-color: #fff; - height: 200px; - width: 233px; - overflow: auto; - overflow-y: scroll; - overflow-x: hidden; - margin: 7px -11px 0; - border: 1px solid #999; - top: 28px; -} - - * html .search-results { - left: 486px; - top: 30px; - } - - -/* search result types */ +/* notes */ -.menu-item a, -.menu-item a:link { - display: block; - padding: 3px 10px 3px 28px; - background-position: 6px 50%; +p.note, +p.alias, +p.related-to { + font-size: 11px; + line-height: 14px; + padding: 5px 20px 5px 60px; background-repeat: no-repeat; - text-align: left; - text-decoration: none; - color: #333; - border-top: 1px solid #fff; - border-bottom: 1px solid #fff; - white-space: nowrap; - -} - - .menu-item a:hover, - .menu-item a.highlighted, - #menu .highlighted a { - border-top: 1px solid #69f; - border-bottom: 1px solid #69f; - background-color: #f0f0ff; - } - - -.menu-item a.section { - font-weight: bold; - background-image: url(../images/section.png); -} - -.menu-item a.class_method, -.menu-item a.instance_method { - background-image: url(../images/method.png); -} - -.menu-item a.class { - background-image: url(../images/class.png); -} - -.menu-item a.constructor { - background-image: url(../images/constructor.png); -} - -.menu-item a.class_property { - background-image: url(../images/class_property.png); + background-position: 20px 50%; + border: 1px solid #000; } -.menu-item a.instance_property { - background-image: url(../images/instance_property.png); +p.note { + background-color: #f0f0f4; + background-image: url(../images/information.png); + border-color: #69c; } -.menu-item a.namespace { - background-image: url(../images/namespace.png); +p.alias { + background-color: #fff6de; + background-image: url(../images/alias.png); + border-color: #cc9; } -.menu-item a.utility { - background-image: url(../images/utility.png); +p.related-to { + background-color: #f4f0f4; + background-image: url(../images/related_to.png); + border-color: #c9c; } - /* halo around selected method */ .highlighter { @@ -364,124 +472,223 @@ ul.section-list { border-radius: 15px; } -/* MENU */ - -div#menu { - width: 960px; - margin: 0 auto; - position: relative; -} - - #menu .api-box h2 a, - #menu .search-box { - width: 213px; - height: 25px; - line-height: 25px; - padding: 5px 10px; - margin-right: 5px; - text-align: center; - float: right; - } - - * html #menu .api-box h2 a, - * html #menu .search-box { - height: 30px; - line-height: 30px; - } + +/* SIDEBAR */ + +#sidebar { + position: fixed; + top: 0; + bottom: 0; + left: 0; + width: 240px; + background: #fff; + font-size: 13px; +} + +#sidebar form.search-ribbon { + margin: 0; + height: 24px; + border-right: 1px solid #636363; + padding: 4px 0 3px; + border-bottom: 1px solid #587e93; + background: url(../images/search-background.png) repeat-x 0 0; + text-align: center; +} + +/* Keep these around for `Control.Tabs`. */ +.sidebar-tabs { display: none; } - #menu .api-box { +.menu-items ul { + margin: 0; +} + + .menu-items ul li { + list-style-type: none; + padding-left: 0; + margin-left: 0; + } + + .menu-items ul .menu-item a { + padding-left: 38px; + background-position: 16px 50%; } - #menu .api-box h2 a { - font-size: 14px; - font-weight: normal; - font-family: Verdana, sans-serif; + .menu-items ul ul .menu-item a { + padding-left: 48px; + background-position: 26px 50%; + } + + .menu-items ul ul ul .menu-item a { + padding-left: 58px; + background-position: 36px 50%; + } + + .menu-items ul ul ul ul .menu-item a { + padding-left: 68px; + background-position: 46px 50%; + } + + + + .menu-item a, + .menu-item a:link { display: block; - background-color: #cde0fb; - border: 1px solid #669; - border-top: 0; + padding: 3px 10px 3px 28px; + background-position: 6px 50%; + background-repeat: no-repeat; + text-align: left; text-decoration: none; - color: #222; + color: #333; + border-top: 1px solid #fff; + border-bottom: 1px solid #fff; + white-space: nowrap; } - - #menu .api-box .menu-items { - position: absolute; - background-color: #fff; - height: 200px; - width: 233px; - overflow: auto; - overflow-y: scroll; - overflow-x: hidden; - top: 35px; - border: 1px solid #999; - right: 5px; + + .menu-item a:hover, + .menu-item a.highlighted, + #sidebar .highlighted a { + border-top: 1px solid #69f; + border-bottom: 1px solid #69f; + background-color: #f0f0ff; + } + + .menu-item a.class_method, + .menu-item a.instance_method { + background-image: url(../images/method.png); + } + + .menu-item a.class { + background-image: url(../images/class.png); + } + + .menu-item a.constructor { + background-image: url(../images/constructor.png); + } + + .menu-item a.class_property { + background-image: url(../images/class_property.png); + } + + .menu-item a.instance_property { + background-image: url(../images/instance_property.png); + } + + .menu-item a.namespace { + background-image: url(../images/namespace.png); } - * html #menu .api-box .menu-items { - right: 10px; - top: 37px; - } + .menu-item a.mixin { + background-image: url(../images/mixin.png); + } + + .menu-item a.utility { + background-image: url(../images/utility.png); + } - #menu .api-box ul, - #menu .api-box li { - margin: 0; - padding: 0; - } + .menu-item a.section { + margin: 0; + background-image: url(../images/section.png); + } + + #api_menu .menu-item a.section { + height: 22px; + font-weight: normal; + color: #a5a5a5; + margin-top: 0; + margin-bottom: 0; + border-top: 1px solid #000; + border-bottom: 1px solid #000; + background: url(../images/section-background.png) repeat-x 0 0; + } - #menu .api-box .menu-item a { - } +.menu-section { + margin-bottom: 1.0em; +} - #menu .search-box { - background-color: #cee8c3; - border: 1px solid #696; - border-top: 0; +#menu_pane { + position: absolute; + top: 32px; + bottom: 0; + left: 0; + width: 239px; + border-right: 1px solid #636363; + overflow-y: scroll; + overflow-x: hidden; +} + +#search_pane { + width: 239px; + border-right: 1px solid #636363; + position: absolute; + top: 32px; + bottom: 0; + left: 0; +} + + #search_results { + margin: 0; + padding: 0; + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + top: 0; + bottom: 0; + width: 239px; } - #menu .search-box input { - width: 150px; - padding: 3px 10px; - margin-top: 2px; - border: 1px solid #999; - border-radius: 10px; - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - } - -#menu #search.ghosted { +#search { + width: 200px; + padding: 2px 3px; +} + +input.ghosted { + font-style: italic; color: #aaa; - text-align: left; } - -/* notes */ -p.note, -p.alias, -p.related-to { - font-size: 11px; - line-height: 14px; - padding: 5px 5px 5px 60px; - background-repeat: no-repeat; - background-position: 20px 50%; - border: 1px solid #000; -} +/* FOOTER */ -p.note { - background-color: #f0f0f4; - background-image: url(../images/information.png); - border-color: #69c; +#footer { + color: #bbb; + width: 595px; + margin-left: 120px; + margin-top: 30px; } -p.alias { - background-color: #fff6de; - background-image: url(../images/alias.png); - border-color: #cc9; -} + #footer .content { + border-top: 1px solid #ccc; + margin: 0; + padding: 30px 0; + } -p.related-to { - background-color: #f4f0f4; - background-image: url(../images/related_to.png); - border-color: #c9c; -} + #footer a, + #footer a:link { + color: #999; + } + .cc { + float: left; + width: 360px; + } + + .cc span { + float: left; + width: 110px; + margin-right: 10px; + text-align: center; + padding-top: 6px; + } + + .cc p { + float: left; + width: 180px; + } + + .credits { + float: left; + width: 235px; + } + + diff --git a/templates/html/assets/stylesheets/core.css b/templates/html/assets/stylesheets/core.css deleted file mode 100644 index 6378bfa3a..000000000 --- a/templates/html/assets/stylesheets/core.css +++ /dev/null @@ -1,415 +0,0 @@ -/* The "section" class implicitly needs a clearfix; adding it here for convenience. */ - -.clearfix:after, -.section:after { - content: "."; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; -} - -.clearfix, .section { - display: inline-block; -} - -html[xmlns] .clearfix, -html[xmlns] .section { - display: block; -} - -* html .clearfix, -* html .section { - height: 1%; -} - -span.replaced { visibility: hidden; } -span.hidden { display: none; } - - -body { - font-family: Verdana, sans-serif; - font-size: 82.5%; - line-height: 1.5em; - margin: 0; - padding: 0; -} - - body * { - margin: 0; - padding: 0; - } - - -h1, h2, h3, h4, h5, h6 { - font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; -} - -h4 { - font-size: 17px; - color: #555; - margin: 1.0em 0 0.6em; -} - -h5 { - font-size: 15px; - color: #2a2a2a; - margin: 1.0em 0 0.6em; -} - -h6 { - font-size: 14px; - color: #000; - margin: 1.0em 0 0.6em; -} - -a img { - border: 0; -} - -ul, li { - list-style-type: none; -} - -ol, ul { - margin: 0 0 15px; -} - -#page a.img, -#page a.img:link, -#page a.img:visited { - border: none; -} - -/* Link bar */ - -div#links { - margin: 0 auto; - width: 835px; - padding: 16px 0; - height: 16px; - overflow: hidden; -} - -div#links_wrapper { - background: #fff; -} - -div#links li { - display: inline; -} - -div#links li a { - color: #666; -} - -div#links li.selected a { - color: #000; - font-weight: bold; - text-decoration: none; -} - -ul#internal_links { - float: left; -} - -ul#internal_links li { - margin-right: 25px; -} - -ul#external_links { - float: right; -} - -ul#external_links li { - margin-left: 25px; - padding-left: 21px; -} - -li#scripty_link { - background: url(http://prototype.conio.net/new/images/link-logo-scripty.png) no-repeat center left; -} - -li#rails_link { - background: url(http://prototype.conio.net/new/images/link-logo-rails.png) no-repeat center left; -} - - - -p a, p a:link, -h1 a, h1 a:link, -h2 a, h2 a:link, -h3 a, h3 a:link, -h4 a, h4 a:link, -h5 a, h5 a:link, -h6 a, h6 a:link { - color: #036; - border-bottom: 1px solid #036; - text-decoration: none; -} - - p a:visited { - border-bottom: 1px solid #666; - } - -code { - font-family: "Panic Sans", "Bitstream Vera Sans Mono", Monaco, Consolas, Andale Mono, monospace; - font-size: 13px; -} - -p code, -li code { - background-color: #f0f0f0; - border: 1px solid #ccc; - border-radius: 3px; - -webkit-border-radius: 3px; - padding: 0 3px; -} - -pre code { - background-color: transparent; - border: 0; - padding: 0; -} - - -#page { - margin: 0 auto; - padding-bottom: 100px; /* FIXME: Temporary as pages are built */ -} - - -/* top */ - -.related-links { - width: 835px; - font-size: 0.9em; - margin: 0 auto 10px; - padding: 10px 0 0; -} - - .related-links a { - color: #000; - text-decoration: none; - } - - .related-links ul { - list-style-type: none; - } - - .related-links ul.internal { - float: left; - width: 355px; - } - - .related-links ul.internal li { - text-align: center; - } - - .related-links ul.external { - float: right; - width: 295px; - } - - .related-links li { - float: left; - padding: 0 15px; - width: 85px; - margin-right: 5px; - } - - .related-links li.last { - margin-right: 0; - } - - .related-links ul.external li.scripty { - padding: 0 8px 0 22px; - background: url(../images/icon-scripty.png) no-repeat; - margin-right: 65px; - } - - .related-links ul.external li.rails { - padding: 0 8px 0 22px; - background: url(../images/icon-rails.png) no-repeat; - } - - -.banner { - height: 152px; - padding: 1px 0; - background: url(../images/header-stripe.png) repeat-x; -} - - .banner-content { - width: 835px; - margin: 0 auto; - } - - .banner h1 { - width: 236px; - height: 150px; - background: url(../images/header-logo.png) no-repeat; - } - - .banner h1 span { - display: none; - } - -.banner-small { - height: 75px; - padding: 1px 0; - background: url(../images/header-stripe-small.png) repeat-x; -} - - .banner-small h1 { - width: 118px; - height: 75px; - background: url(../images/header-logo-small.png) no-repeat; - margin-left: 60px; - } - - .banner-small h1 span { - display: none; - } - - -/* PAGE CONTENT */ - -.page-content { - width: 955px; - margin: 30px auto 0; -} - - .page-content .page-title { - margin-left: 120px; - margin-bottom: 15px; - font-size: 27px; - letter-spacing: -1px; - color: #444; - } - - .page-content .page-introduction { - margin-left: 120px; - margin-bottom: 25px; - } - -.page-content .section { - width: 955px; - margin: 10px 0 20px; -} - - .page-content .section .section-title { - width: 110px; - margin-right: 10px; - padding-right: 0; - float: left; - text-align: right; - overflow: hidden; - } - - .page-content .section .section-title h3 { - color: #999; - font-size: 14px; - line-height: 110%; - padding-right: 10px; - padding-bottom: 5px; - } - - .page-content .section .section-content { - width: 835px; - float: left; - } - -.page-content a, -.page-content a:link { - color: #036; - border-bottom: 1px solid #036; - text-decoration: none; -} - - .page-content a:visited { - border-bottom: 1px solid #bbb; - } - -.page-content ul, -.page-content ol { - margin: 10px 0; -} - -.page-content li { - margin: 5px 0 8px 20px; - list-style-type: disc; -} - -.page-content p { - margin: 0 0 0.8em; -} - -.page-content pre { - color: #333; - background-color: #f0f0ff; - border: 1px solid #dde; - padding: 3px 5px; - margin: 0 0 1em; -} - -.page-content .two-column { - -} - - .page-content .two-column-left, - .page-content .two-column-right { - width: 475px; - margin-right: 5px; - float: left; - } - - .page-content .two-column-right { - margin-right: 0; - } - - .page-content .two-column .section { - width: 475px; - } - - .page-content .two-column .section-content { - width: 345px; - padding-right: 10px; - } - - - -.smallcaps { - font-size: 0.85em; - text-transform: uppercase; - letter-spacing: 1px; -} - - -/* MASTHEAD */ - -div#masthead { - margin-top: 50px; - background: url(../images/header-stripe-small.png) repeat-x top left; - height: 76px; -} - -div#masthead div#masthead_content { - margin: 0 auto; - width: 835px; - position: relative; -} - -div#masthead h1#logo { - background: url(../images/header-logo-small.png) no-repeat 0 1px; - width: 118px; - height: 76px; - position: relative; - left: 60px; -} - -div#masthead a { - text-decoration: none; -} - diff --git a/templates/html/assets/stylesheets/grid.css b/templates/html/assets/stylesheets/grid.css deleted file mode 100644 index 08824c539..000000000 --- a/templates/html/assets/stylesheets/grid.css +++ /dev/null @@ -1,13 +0,0 @@ -body.grid { - width: 955px; - margin: auto; -} - -body.grid div#page { - overflow: hidden; - background: url(../images/grid.png); -} - -body.grid div#page * { - opacity: .9; -} diff --git a/templates/html/assets/stylesheets/highlighter.css b/templates/html/assets/stylesheets/highlighter.css deleted file mode 100644 index 8ce358fe6..000000000 --- a/templates/html/assets/stylesheets/highlighter.css +++ /dev/null @@ -1,116 +0,0 @@ -/* See license.txt for terms of usage */ - -.firebugHighlight { - z-index: 2147483647; - position: absolute; - background-color: #3875d7; -} - -.firebugLayoutBoxParent { - z-index: 2147483647; - position: absolute; - border-right: 1px dashed #BBBBBB; - border-bottom: 1px dashed #BBBBBB; -} - -.firebugRulerH { - position: absolute; - top: -15px; - left: 0; - width: 100%; - height: 14px; - background: url(chrome://firebug/skin/rulerH.png) repeat-x; - border-top: 1px solid #BBBBBB; - border-right: 1px dashed #BBBBBB; - border-bottom: 1px solid #000000; -} - -.firebugRulerV { - position: absolute; - top: 0; - left: -15px; - width: 14px; - height: 100%; - background: url(chrome://firebug/skin/rulerV.png) repeat-y; - border-left: 1px solid #BBBBBB; - border-right: 1px solid #000000; - border-bottom: 1px dashed #BBBBBB; -} - -.overflowRulerX > .firebugRulerV { - left: 0; -} - -.overflowRulerY > .firebugRulerH { - top: 0; -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -.firebugLayoutBoxOffset { - z-index: 2147483647; - position: absolute; - opacity: 0.8; -} - -.firebugLayoutBoxMargin { - background-color: #EDFF64; -} - -.firebugLayoutBoxBorder { - background-color: #666666; -} - -.firebugLayoutBoxPadding { - background-color: SlateBlue; -} - -.firebugLayoutBoxContent { - background-color: SkyBlue; -} - -/*.firebugHighlightGroup .firebugLayoutBox { - background-color: transparent; -} - -.firebugHighlightBox { - background-color: Blue !important; -}*/ - -.firebugLayoutLine { - z-index: 2147483647; - background-color: #000000; - opacity: 0.4; -} - -.firebugLayoutLineLeft, -.firebugLayoutLineRight { - position: fixed; - width: 1px; - height: 100%; -} - -.firebugLayoutLineTop, -.firebugLayoutLineBottom { - position: absolute; - width: 100%; - height: 1px; -} - -.firebugLayoutLineTop { - margin-top: -1px; - border-top: 1px solid #999999; -} - -.firebugLayoutLineRight { - border-right: 1px solid #999999; -} - -.firebugLayoutLineBottom { - border-bottom: 1px solid #999999; -} - -.firebugLayoutLineLeft { - margin-left: -1px; - border-left: 1px solid #999999; -} diff --git a/templates/html/assets/stylesheets/main.css b/templates/html/assets/stylesheets/main.css deleted file mode 100644 index b83efd631..000000000 --- a/templates/html/assets/stylesheets/main.css +++ /dev/null @@ -1,443 +0,0 @@ -/* @group Tags */ - -body { - font-family: Verdana, sans-serif; -} - -form { - margin: 0; - padding: 0; -} - -a { - color: #036; - text-decoration: none; -} - - p a { - border-bottom: 1px solid #999; - padding: 0 1px; - } - - p a:hover { - background-color: #036; - color: #fff; - border-bottom: 1px solid #036; - } - -h1, h2, h3, h4, h5, h6 { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - margin: 0; - padding: 0; -} - -pre { - padding: 0; -} - -code { - font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", monospace; - font-size: 11px; -} - - -/* @end */ - -/* @group Masthead */ - -div#masthead { - background: url(../images/header-stripe-small.png) repeat-x top left; - height: 76px; -} - -div#masthead div#masthead_content { - margin: 0 auto; - width: 835px; - position: relative; -} - -div#masthead h1#logo { - background: url(../images/header-logo-small.png) no-repeat 0 1px; - width: 118px; - height: 76px; - position: relative; - left: 60px; -} - - - -/* @end */ - -/* @group Main */ - -#main { - width: 840px; - margin: 20px auto; - position: relative; -} - - #main h2 { - color: #444; - font-size: 22px; - padding-left: 69px; - margin-top: 0; - } - - #main h2 span { - color: #ccc; - display: block; - text-transform: uppercase; - font-size: 13px; - margin-bottom: -10px; - padding-left: 2px; - } - - #main h2 a { - color: #444; - text-decoration: none; - } - - #main h2 a:hover { - color: #222; - border-bottom: 1px solid #999; - } - - #main h4, h5, h6 { - padding-left: 4px; - } - - #main li h4, - #main li h5, - #main li h6 { - padding-left: 0; - } - - #main h4.inherited { - color: #888 !important; - } - - - #main .section { - overflow: hidden; - padding-left: 65px; - padding-bottom: 0; - margin: 5px 0 0; - } - - #main .section h3 { - position: absolute; - left: -85px; - width: 110px; - text-align: right; - font-size: 13px; - padding-top: 3px; - color: #999; - line-height: 100%; - min-height: 30px; - } - - #main .section h4 { - font-size: 15px; - color: #444; - margin: 0 0 0.3em; - } - - #main .section h4.inherited { - color: #888; - } - - #main .section h4.inherited a { - color: #49B; - } - - #main p { - margin: 0 0 1.1em; - } - - #main pre { - background-color: #000; - color: #fff; - padding: 5px 10px; - margin: 0 0 5px; - } - - #main pre.syntax { - background-color: #f5f5f5; - color: #000; - padding: 3px 5px; - } - -#main .section p.purpose { - background-color: #CDE0FB; - padding: 3px 5px; -} - -#main .section p { - padding: 0 5px; -} - -#main ul, #main ol { - padding-left: 5px; - margin: 0 0 10px; -} - -#main ul, #main ul li { - list-style-type: none; -} - - -#main #excerpt p { - color: #000; - border-left: 3px solid #bbb; - padding: 0; - margin-left: -10px; - font-size: 94%; - padding-left: 10px; -} - -#main .meta { - position: absolute; - width: 108px; - left: -60px; - text-align: right; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: bold; - color: #888; -} - - - -/* @end */ - -/* @group Method List */ - -ul.method-list li { - display: inline; - border-top: 1px solid #ccc; -} - - ul.method-list li a { - background-color: #eee; - padding: 2px 3px; - border: 1px solid #ccc; - } - - ul.method-list li a:hover { - background-color: #ddd; - } - - - -/* @end */ - -/* @group Menu */ - -#menu { - width: 840px; - margin: 0 auto; - position: relative; - height: 25px; -} - -#menu div { - width: 234px; - float: right; - margin: 0 3px; -} - - #menu div.search-box { - width: 222px; - background-color: #cee8c3; - font-size: 13px; - height: 25px; - line-height: 25px; - padding: 2px 6px; - } - - #menu div.search-box label { - color: #555; - } - - #menu div.search-box input { - border: 1px solid #bbb; - padding: 2px; - width: 163px; - } - - -#menu h2 { - font-size: 13px; - font-weight: normal; -} - - #menu h2 a { - height: 25px; - line-height: 25px; - display: block; - text-align: center; - background-color: #CDE0FB; - font-family: Verdana, sans-serif; - padding: 2px 6px; - } - - #menu h2 a:hover { - background-color: #a4c8fb; - } - - #menu #api_menu, - #menu #search_results { - border: 1px solid #ddd; - background: #fff; - position: absolute; - width: 232px; - top: 26px; - z-index: 1500; - max-height: 200px; - overflow: auto; - } - - #menu #api_menu { - right: 3px; - } - - #menu #search_results { - right: 243px; - } - - - #menu .menu-items li { - background-color: #fff; - } - - #menu .menu-items li a { - display: block; - color: #333; - background-color: #fff; - padding: 2px 6px; - border-top: 1px solid #fff; - border-bottom: 1px solid #fff; - } - - #menu .menu-items li a:hover, - #menu .menu-items li.highlighted a { - background-color: #CDE0FB; - border-top-color: #a4c8fb; - border-bottom-color: #a4c8fb; - } - - - #menu .menu-items ul { - margin: 0; - padding: 0; - } - -#menu .menu-items li a { - background-repeat: no-repeat; - background-position: 6px 3px; - padding: 3px 5px 3px 30px; - font-size: 12px; -} - - #menu .menu-items li a.class { - background-image: url(../images/class.png); - } - - #menu .menu-items li a.namespace { - background-image: url(../images/namespace.png); - } - - #menu .menu-items li a.mixin { - background-image: url(../images/mixin.png); - } - - #menu .menu-items li a.section { - text-transform: uppercase; - color: #777; - background-image: url(../images/section.png); - } - - #menu .menu-items li a.class_method, - #menu .menu-items li a.instance_method, - #menu .menu-items li a.utility { - background-image: url(../images/method.png); - } - - #menu .menu-items li a.class_property, - #menu .menu-items li a.instance_property { - background-image: url(../images/property.png); - } - - #menu .menu-items li a.constructor { - background-image: url(../images/constructor.png); - } - - #menu .menu-items li a.constant { - background-image: url(../images/constant.png); - } - - -/* @end */ - -/* @group Section Headings (H3s) */ - -.section h3 { - padding-right: 25px; - background-repeat: no-repeat; - background-position: right 4px; -} - -.section-superclass h3 { - background-image: url(../images/class.png); -} - -.section-method-list h3, -.section-instance_methods h3, -.section-klass_methods h3 { - background-image: url(../images/method.png); -} - -.section-klass_properties h3, -.section-instance_properties h3 { - background-image: url(../images/property.png); -} - -.section-constructor h3 { - background-image: url(../images/constructor.png); -} - -.section-constants h3 { - background-image: url(../images/constant.png); -} - -.section-mixins h3 { - background-image: url(../images/mixin.png); -} - - -/* @end */ - - - -/* @group Method Details List */ - -ul.method-details-list li { - border: 2px solid #fff; -} - -ul.method-details-list li.highlighted { - border: 2px solid #CDE0FB; -} - -/* @end */ - - -/* floating menu */ - -#menu.fixed { - position: fixed; - top: 0; - z-index: 1000; - left: 232px; -} - diff --git a/templates/html/assets/stylesheets/screen.css b/templates/html/assets/stylesheets/screen.css deleted file mode 100644 index 4fe507799..000000000 --- a/templates/html/assets/stylesheets/screen.css +++ /dev/null @@ -1,244 +0,0 @@ - - -/* Masthead */ - -div#masthead { - background: url(http://prototype.conio.net/new/images/header-stripe.png) repeat-x top left; - height: 152px; -} - -div#masthead div#masthead_content { - margin: 0 auto; - width: 835px; - position: relative; -} - -div#masthead h1#logo { - background: url(http://prototype.conio.net/new/images/header-logo.png) no-repeat 0 1px; - width: 236px; - height: 152px; -} - -div#masthead div#pitch { - position: absolute; - background: url(http://prototype.conio.net/new/images/header-copy.png) no-repeat 0 1px; - top: 0; - left: 300px; - width: 535px; - height: 152px; -} - -div#masthead h1#logo *, -div#masthead div#pitch * { - display: none; -} - - -/* Buttons */ - -div#buttons, div#more { - overflow: hidden; - width: 840px; - margin: -1px auto 0 auto; - padding: 0 0 0 5px; -} - -div#more { - margin-top: 35px; -} - -div#buttons li { - display: block; - float: left; - border-top: 1px solid #929fb3; - margin: 0 5px 0 0; - width: 175px; - height: 100px; -} - -div#more li { - height: 50px; -} - -div#buttons li a { - display: block; - position: relative; - width: 175px; - height: 100px; - text-decoration: none; - background: #cde0fb no-repeat center 30px; -} - -div#buttons li a:hover { - background-color: #a4c8fb; -} - -div#buttons li a span.title { - display: none; -} - -div#buttons li a span.description { - position: absolute; - display: block; - width: 175px; - top: 55px; - text-align: center; - color: #666; -} - -div#more li a span.title { - position: absolute; - display: block; - width: 175px; - top: 15px; - text-align: center; - color: #666; -} - -div#buttons li a:hover span.description { - color: #333; -} - -div#buttons li#download_button { - width: 295px; -} - -div#buttons li#download_button a, -div#buttons li#download_button a span.description { - width: 295px; -} - -div#buttons li#download_button a { - background-color: #cee8c3; - background-image: url(http://prototype.conio.net/new/images/button-download.png); -} - -div#buttons li#download_button a:hover { - background-color: #a4e289; -} - -div#buttons li#learn_button a { - background-image: url(http://prototype.conio.net/new/images/button-learn.png); -} - -div#buttons li#discuss_button a { - background-image: url(http://prototype.conio.net/new/images/button-discuss.png); -} - -div#buttons li#contribute_button a { - background-image: url(http://prototype.conio.net/new/images/button-contribute.png); -} - -div#more { - position: relative; - height: 400px; - margin: 15px 60px; - color: #444; -} - - div#more .column { - display: block; - float: left; - margin: 0 5px 0 0; - padding-left: 5px; - min-height: 300px; - } - - div#more h3 { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - letter-spacing: -1px; - font-weight: bold; - color: #444; - font-size: 18px; - margin: 0 0 5px; - } - -.column { - width: 175px; -} - -.wide-column { - width: 295px; -} - -.medium-column { - width: 235px; -} - -.main-column { - width: 530px; -} - -.sidebar-column { - width: 290px; -} - -div#more { - width: 840px; - margin: 25px auto 0; -} - -/*#buttons_wrapper { - background-color: #0E4FAF; -} - -#page { - background-color: #292929; -} -*/ - -div#more div.main-column p { - font-size: 16px; - margin-right: 10px; -} - -div#more h3.tagline { - height: 23px; - background: url(../images/tagline.png) no-repeat top left; - width: 530px; -} - - div#more h3.tagline span { display: none; } - -.view-master { - text-align: center; - font-family: Verdana; - color: #ccc; - font-size: 18px; - padding: 35px 0; - margin: 10px 0; -} - -.using-prototype { - border-top: 2px solid #ddd; - border-bottom: 2px solid #ddd; - margin: 25px 10px 10px 0; - padding: 5px 0; -} - -.sidebar-column h4 { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: bold; - margin: 0; -} - -.sidebar-column p { - margin-bottom: 15px; -} - -ol, ol li { - list-style-type: decimal; - line-height: 160%; -} - - ol li { - margin: 0 0 2px; - } - -ol, ul { - margin: 0 0 15px; - padding-left: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - diff --git a/templates/html/helpers.rb b/templates/html/helpers.rb index 1769b9d74..15e09f130 100644 --- a/templates/html/helpers.rb +++ b/templates/html/helpers.rb @@ -12,8 +12,21 @@ module CodeHelper end module MenuHelper + def menu(obj) + class_names = menu_class_name(obj) + li_class_names = obj.type == "section" ? "menu-section" : "" + html = <<-EOS + + EOS + unless obj.children.empty? + html << content_tag(:ul, obj.children.map { |n|menu(n) }.join("\n"), :class => li_class_names) + end + content_tag(:li, html, :class => class_names) + end end end end end -end +end \ No newline at end of file diff --git a/templates/html/index.erb b/templates/html/index.erb index b2b1e3717..2f548a4d4 100644 --- a/templates/html/index.erb +++ b/templates/html/index.erb @@ -21,4 +21,4 @@ <% end %> - \ No newline at end of file + \ No newline at end of file diff --git a/templates/html/layout.erb b/templates/html/layout.erb index 5ee9e4336..6e6aed82a 100644 --- a/templates/html/layout.erb +++ b/templates/html/layout.erb @@ -1,50 +1,61 @@ - + - - - Prototype API documentation | <%= @title %> - - - <%= javascript_include_tag "prototype", "effects", "controls" %> - <%= javascript_include_tag "application", "code_highlighter" %> + + + Prototype API documentation | <%= @title %> + + + <%= javascript_include_tag "prototype" %> + <%= javascript_include_tag "application", "code_highlighter", "tabs" %> + <%= javascript_include_tag "item_index" %> + + <%= stylesheet_link_tag "api" %> + + + + - <%= javascript_include_tag "item_index" %> - - <%= stylesheet_link_tag "core", "api" %> - - - - -
- - + - +
+
<%= @content_for_layout %> @@ -52,28 +63,24 @@
- +
+ diff --git a/templates/html/partials/short_description.erb b/templates/html/partials/short_description.erb index 73d3faba0..c4ad7848d 100644 --- a/templates/html/partials/short_description.erb +++ b/templates/html/partials/short_description.erb @@ -11,9 +11,9 @@
    <% object.arguments.each do |arg| %>
  • - <%= arg.name %> + <%= arg.name %> <% unless arg.types.empty? %> - (<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>) + (<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>) <% end %> <%= ' – ' + inline_htmlize(arg.description) unless arg.description.empty? %>
  • From 58a2f9db2821704ce2f77d92323d2a72b852ed79 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 15 Sep 2009 18:27:46 -0500 Subject: [PATCH 003/502] Update to latest PDoc. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index 147250bd6..e976b8441 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b +Subproject commit e976b844144666113008a9159fa501f8f575bd1c From f4ea4c6ef7f48dca963e54fc6203350b2d233b97 Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Tue, 1 Sep 2009 15:59:47 +0200 Subject: [PATCH 004/502] Rewrite `String#camelize` using `String#replace` with a replacement function [#297 state:resolved] --- src/lang/string.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/lang/string.js b/src/lang/string.js index e14118020..16935a70e 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -311,17 +311,9 @@ Object.extend(String.prototype, (function() { * // -> 'MozBinding' **/ function camelize() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); - - return camelized; + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); } /** From 3b525f194d0e573923c7d9ad1cbd0cf63a849954 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Mon, 28 Sep 2009 19:21:37 -0400 Subject: [PATCH 005/502] String#startsWith, String#endsWith performance optimization [#808 state:resolved] --- CHANGELOG | 4 ++++ src/lang/string.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f5df87f42..893408365 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +* String#startsWith, String#endsWith performance optimization (Yaffle, Tobie Langel, kangax) + +* Rewrite String#camelize using String#replace with a replacement function (Phred, John-David Dalton, Samuel Lebeau, kangax) + *1.6.1* (August 24, 2009) * Avoid triggering a warning when Java is disabled in IE8. [#668 state:resolved] (orv, kangax, Andrew Dupont, Tobie Langel) diff --git a/src/lang/string.js b/src/lang/string.js index 16935a70e..3c7f23f44 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -429,7 +429,7 @@ Object.extend(String.prototype, (function() { * Checks if the string starts with `substring`. **/ function startsWith(pattern) { - return this.indexOf(pattern) === 0; + return !this.lastIndexOf(pattern,0); } /** @@ -439,7 +439,7 @@ Object.extend(String.prototype, (function() { **/ function endsWith(pattern) { var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; + return d >= 0 && this.indexOf(pattern,d) === d; } /** From d2874c02940685b4257b88a7cb478d776f89bf70 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Thu, 8 Oct 2009 14:46:14 +0100 Subject: [PATCH 006/502] doc: Merged old docs for Element.addMethods. --- src/dom/dom.js | 120 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3fae168ad..ac787e4d7 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1758,12 +1758,126 @@ Element.hasAttribute = function(element, attribute) { /** * Element.addMethods(methods) -> undefined * Element.addMethods(tagName, methods) -> undefined + * - tagName (String): (Optional) The name of the HTML tag for which the + * methods should be available; if not given, all HTML elements will have + * the new methods. + * - methods (Object): A hash of methods to add. * - * Takes a hash of methods and makes them available as methods of extended - * elements and of the `Element` object. + * `Element.addMethods` makes it possible to mix your *own* methods into the + * `Element` object and extended element instances (all of them, or only ones + * with the given HTML tag if you specify `tagName`). * - * The second usage form is for adding methods only to specific tag names. + * You define the methods in a hash that you provide to `Element.addMethods`. + * Here's an example adding two methods: * + * Element.addMethods({ + * + * // myOwnMethod: Do something cool with the element + * myOwnMethod: function(element) { + * if (!(element = $(element))) return; + * // ...do smething with 'element'... + * return element; + * }, + * + * // wrap: Wrap the element in a new element using the given tag + * wrap: function(element, tagName) { + * var wrapper; + * if (!(element = $(element))) return; + * wrapper = new Element(tagName); + * element.parentNode.replaceChild(wrapper, element); + * wrapper.appendChild(element); + * return wrapper; + * } + * + * }); + * + * Once added, those can be used either via `Element`: + * + * // Wrap the element with the ID 'foo' in a div + * Element.wrap('foo', 'div'); + * + * ...or as instance methods of extended elements: + * + * // Wrap the element with the ID 'foo' in a div + * $('foo').wrap('div'); + * + * Note the following requirements and conventions for methods added to + * `Element`: + * + * - The first argument is *always* an element or ID, by convention this + * argument is called `element`. + * - The method passes the `element` argument through [[$]] and typically + * returns if the result is undefined. + * - Barring a good reason to return something else, the method returns the + * extended element to enable chaining. + * + * Our `myOwnMethod` method above returns the element because it doesn't have + * a good reason to return anything else. Our `wrap` method returns the + * wrapper, because that makes more sense for that method. + * + * ##### Extending only specific elements + * + * If you call `Element.addMethods` with *two* arguments, it will apply the + * methods only to elements with the given HTML tag: + * + * Element.addMethods('DIV', my_div_methods); + * // the given methods are now available on DIV elements, but not others + * + * You can also pass an *array* of tag names as the first argument: + * + * Element.addMethods(['DIV', 'SPAN'], my_additional_methods); + * // DIV and SPAN now both have the given methods + * + * (Tag names in the first argument are not case sensitive.) + * + * Note: `Element.addMethods` has built-in security which prevents you from + * overriding native element methods or properties (like `getAttribute` or + * `innerHTML`), but nothing prevents you from overriding one of Prototype's + * methods. Prototype uses a lot of its methods internally; overriding its + * methods is best avoided or at least done only with great care. + * + * ##### Example 1 + * + * Our `wrap` method earlier was a complete example. For instance, given this + * paragraph: + * + * language: html + *

    Some content...

    + * + * ...we might wrap it in a `div`: + * + * $('first').wrap('div'); + * + * ...or perhaps wrap it and apply some style to the `div` as well: + * + * $('first').wrap('div').setStyle({ + * backgroundImage: 'url(images/rounded-corner-top-left.png) top left' + * }); + * + * ##### Example 2 + * + * We can add a method to elements that makes it a bit easier to update them + * via [[Ajax.Updater]]: + * + * Element.addMethods({ + * ajaxUpdate: function(element, url, options) { + * if (!(element = $(element))) return; + * element.update('Loading...'); + * options = options || {}; + * options.onFailure = options.onFailure || defaultFailureHandler.curry(element); + * new Ajax.Updater(element, url, options); + * return element; + * } + * }); + * + * Now we can update an element via an Ajax call much more concisely than + * before: + * + * $('foo').ajaxUpdate('/new/content'); + * + * That will use Ajax.Updater to load new content into the 'foo' element, + * showing a spinner while the call is in progress. It even applies a default + * failure handler (since we didn't supply one). **/ Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; From 7d073ad56a766c71fa6349d28db060ac82312d33 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 13:35:38 +0100 Subject: [PATCH 007/502] doc: Merged/updated old docs for Element overview --- src/dom/dom.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index ac787e4d7..b5f7765f8 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -60,6 +60,32 @@ if (!Node.ELEMENT_NODE) { /** section: DOM * class Element + * + * The `Element` object provides a variety of powerful DOM methods for + * interacting with DOM elements — creating them, updating them, + * traversing them, etc. You can access these either as methods of `Element` + * itself, passing in the element to work with as the first argument, or as + * methods on extended element *instances*: + * + * // Using Element: + * Element.addClassName('target', 'highlighted'); + * + * // Using an extended element instance: + * $('target').addClassName('highlighted'); + * + * `Element` is also a constructor for building element instances from scratch, + * see [`new Element`](#new-constructor) for details. + * + * Most `Element` methods return the element instance, so that you can chain + * them easily: + * + * $('message').addClassName('read').update('I read this message!'); + * + * ##### More Information + * + * For more information about extended elements, check out ["How Prototype + * extends the DOM"](http://prototypejs.org/learn/extensions), which will walk + * you through the inner workings of Prototype's DOM extension mechanism. **/ /** From d6d3ab1fefb30b03b8576864ce975401547eb4c3 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 13:36:14 +0100 Subject: [PATCH 008/502] doc: Merged/updated old docs for Element constructor --- src/dom/dom.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index b5f7765f8..4d44a8353 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -90,11 +90,25 @@ if (!Node.ELEMENT_NODE) { /** * new Element(tagName[, attributes]) - * - tagName (String): The name of the HTML element to create. - * - attributes (Object): A list of attribute/value pairs to set on the - * element. + * - tagName (String): The name of the HTML element to create. + * - attributes (Object): An optional group of attribute/value pairs to set on + * the element. * - * Creates an HTML element with `tagName` as the tag name. + * Creates an HTML element with `tagName` as the tag name, optionally with the + * given attributes. This can be markedly more concise than working directly + * with the DOM methods, and takes advantage of Prototype's workarounds for + * various browser issues with certain attributes: + * + * ##### Example + * + * // The old way: + * var a = document.createElement('a'); + * a.setAttribute('class', 'foo'); + * a.setAttribute('href', '/foo.html'); + * a.appendChild(document.createTextNode("Next page")); + * + * // The new way: + * var a = new Element('a', {'class': 'foo', href: '/foo.html'}).update("Next page"); **/ (function(global) { From 7df62ce864a1cb5e2fd1a5cd48f8e98a2a2bda45 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 13:56:39 +0100 Subject: [PATCH 009/502] doc: Merged/updated old docs for Element.extend --- src/dom/dom.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 4d44a8353..bc047dcd0 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1703,11 +1703,26 @@ Object.extend(Element, Element.Methods); /** * Element.extend(element) -> Element * - * Extends `element` with all of the methods contained in `Element.Methods` - * and `Element.Methods.Simulated`. - * If `element` is an `input`, `textarea`, or `select` tag, it will also be - * extended with the methods from `Form.Element.Methods`. If it is a `form` - * tag, it will also be extended with the methods from `Form.Methods`. + * Extends the given element instance with all of the Prototype goodness and + * syntactic sugar, as well as any extensions added via [[Element.addMethods]]. + * (If the element instance was already extended, this is a no-op.) + * + * You only need to use `Element.extend` on element instances you've acquired + * directly from the DOM; **all** Prototype methods that return element + * instances (such as [[$]], [[Element.down]], etc.) will pre-extend the + * element before returning it. + * + * Check out ["How Prototype extends the + * DOM"](http://prototypejs.org/learn/extensions) for more about element + * extensions. + * + * ##### Details + * + * Specifically, `Element.extend` extends the given instance with the methods + * contained in `Element.Methods` and `Element.Methods.Simulated`. If `element` + * is an `input`, `textarea`, or `select` element, it will also be extended + * with the methods from `Form.Element.Methods`. If it is a `form` element, it + * will also be extended with the methods from `Form.Methods`. **/ Element.extend = (function() { From 77832408bdd6a8ce6e82eef9199ee031c6fbfecc Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 14:14:14 +0100 Subject: [PATCH 010/502] doc: Merged/updated old docs for Element.addClassName --- src/dom/dom.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index bc047dcd0..9870c52b6 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -756,8 +756,24 @@ Element.Methods = { /** * Element.addClassName(@element, className) -> Element + * - className (String): The class name to add. * - * Adds a CSS class to `element`. + * Adds the given CSS class to `element`. + * + * ##### Example + * + * Assuming this HTML: + * + * language: html + *
    + * + * Then: + * + * $('mutsu').className; + * // -> 'apple fruit' + * $('mutsu').addClassName('food'); + * $('mutsu').className; + * // -> 'apple fruit food' **/ addClassName: function(element, className) { if (!(element = $(element))) return; From 402a2d408e8d7946f95e0652d9357b38e61ac2f8 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 14:31:02 +0100 Subject: [PATCH 011/502] doc: Merged/updated old docs for Element.adjacent --- src/dom/dom.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 9870c52b6..3b0f8d8dc 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -646,7 +646,30 @@ Element.Methods = { * - selector (String): A CSS selector. * * Finds all siblings of the current element that match the given - * selector(s). + * selector(s). If you provide multiple selectors, siblings matching *any* + * of the selectors are included. If a sibling matches multiple selectors, + * it is only included once. The order of the returned array is not defined. + * + * ##### Example + * + * Assuming this list: + * + * language: html + *
      + *
    • New York
    • + *
    • London
    • + *
    • Chicago
    • + * + *
    • Los Angeles
    • + *
    • Austin
    • + *
    + * + * Then: + * + * $('nyc').adjacent('li.us'); + * // -> [li#chi, li#la, li#aus] + * $('nyc').adjacent('li.uk', 'li.jp'); + * // -> [li#lon, li#tok] **/ adjacent: function(element) { var args = Array.prototype.slice.call(arguments, 1); From e3c89c08c650e69cce1e31da93f58a52b3568f03 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 14:48:47 +0100 Subject: [PATCH 012/502] doc: Merged/updated old docs for Element.ancestors. --- src/dom/dom.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3b0f8d8dc..050562eca 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -470,8 +470,34 @@ Element.Methods = { /** * Element.ancestors(@element) -> [Element...] * - * Collects all of `element`'s ancestors and returns them as an array of - * elements. + * Collects all of `element`'s ancestor elements and returns them as an + * array of extended elements. + * + * The returned array's first element is `element`'s direct ancestor (its + * `parentNode`), the second one is its grandparent, and so on until the + * `html` element is reached. `html` will always be the last member of the + * array. Calling `ancestors` on the `html` element will return an empty + * array. + * + * ##### Example + * + * Assuming: + * + * language: html + * + * [...] + * + *
    + *
    + *
    + *
    + * + * + * + * Then: + * + * $('kid').ancestors(); + * // -> [div#father, body, html] **/ ancestors: function(element) { return Element.recursivelyCollect(element, 'parentNode'); From 892eb9d6b3b30a26c9ea4ad1d640c6b8792fa2a9 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 15:34:39 +0100 Subject: [PATCH 013/502] doc: Merged/updated old docs for Element.childElements / Element.immediateDescendants. Made immediateDescendants an alias of childElements rather than vice-versa as the latter is depreceated. --- src/dom/dom.js | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 050562eca..37bc8390e 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -527,11 +527,10 @@ Element.Methods = { return $(element); }, - /** + /** deprecated, alias of: Element.childElements * Element.immediateDescendants(@element) -> [Element...] * - * Collects all of `element`'s immediate descendants (i.e., children) and - * returns them as an array of elements. + * **This method is deprecated, please see [[Element.childElements]]**. **/ immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; @@ -1302,8 +1301,40 @@ Object.extend(Element.Methods, { **/ getElementsBySelector: Element.Methods.select, - /** alias of: Element.immediateDescendants + /** * Element.childElements(@element) -> [Element...] + * + * Collects all of the element's children and returns them as an array of + * [extended](http://prototypejs.org/api/element/extend) elements, in + * document order. The first entry in the array is the topmost child of + * `element`, the next is the child after that, etc. + * + * Like all of Prototype's DOM traversal methods, `childElements` ignores + * text nodes and returns element nodes only. + * + * ##### Example + * + * Assuming: + * + * language: html + *
    + * Some text in a text node + *
    + *
    + *
    + *
    + *
    + * + * Then: + * + * $('australopithecus').childElements(); + * // -> [div#homo-erectus] + * + * $('homo-erectus').childElements(); + * // -> [div#homo-neanderthalensis, div#homo-sapiens] + * + * $('homo-sapiens').childElements(); + * // -> [] **/ childElements: Element.Methods.immediateDescendants }); From 1e29e3c6c924db6d9c0b525e11a96c1f71bbab60 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 15:49:54 +0100 Subject: [PATCH 014/502] doc: Merged/updated old docs for Element.classNames, mostly by marking it deprecated. --- src/dom/dom.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 37bc8390e..813a5ae56 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -780,11 +780,15 @@ Element.Methods = { return Element.getDimensions(element).width; }, - /** + /** deprecated * Element.classNames(@element) -> [String...] * * Returns a new instance of [[Element.ClassNames]], an [[Enumerable]] * object used to read and write CSS class names of `element`. + * + * **Deprecated**, please see [[Element.addClassName]], + * [[Element.removeClassName]], and [[Element.hasClassName]]. If you want + * an array of classnames, you can use `$w(element.className)`. **/ classNames: function(element) { return new Element.ClassNames(element); From 9ce1ea06b5e51dedbdb92f40b511f06e6fde131c Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 15:58:32 +0100 Subject: [PATCH 015/502] doc: Merged/updated old docs for Element.cleanWhitespace. --- src/dom/dom.js | 39 ++++++++++++++++++++++++++++++++++++++- vendor/pdoc | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 813a5ae56..48d255fd4 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -860,7 +860,44 @@ Element.Methods = { /** * Element.cleanWhitespace(@element) -> Element * - * Removes whitespace-only text node children from `element`. + * Removes all of `element`'s child text nodes that contain *only* + * whitespace. Returns `element`. + * + * This can be very useful when using standard methods like `nextSibling`, + * `previousSibling`, `firstChild` or `lastChild` to walk the DOM. Usually + * you'd only do that if you are interested in all of the DOM nodes, not + * just Elements (since if you just need to traverse the Elements in the + * DOM tree, you can use [[Element.up]], [[Element.down]], + * [[Element.next]], and [[Element.previous]] instead). + * + * #### Example + * + * Consider the following HTML snippet: + * + * language: html + *
      + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    + * + * Let's grab what we think is the first list item using the raw DOM + * method: + * + * var element = $('apples'); + * element.firstChild.innerHTML; + * // -> undefined + * + * It's undefined because the `firstChild` of the `apples` element is a + * text node containing the whitespace after the end of the `ul` and before + * the first `li`. + * + * If we remove the useless whitespace, then `firstChild` works as expected: + * + * var element = $('apples'); + * element.cleanWhitespace(); + * element.firstChild.innerHTML; + * // -> 'Mutsu' **/ cleanWhitespace: function(element) { element = $(element); diff --git a/vendor/pdoc b/vendor/pdoc index 147250bd6..fa1187431 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b +Subproject commit fa1187431a71d43c7b292feecd2dc5a61a6f630b From c448b38f7b955f26153e6925978678ca0db26057 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 16:55:42 +0100 Subject: [PATCH 016/502] doc: Merged/updated old docs for Element.clonePosition. --- src/dom/dom.js | 63 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 48d255fd4..ec3631cf3 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1286,15 +1286,60 @@ Element.Methods = { /** * Element.clonePosition(@element, source[, options]) -> Element - * - * Clones the position and/or dimensions of `source` onto `element` as - * defined by `options`. - * - * Valid keys for `options` are: `setLeft`, `setTop`, `setWidth`, and - * `setHeight` (all booleans which default to `true`); and `offsetTop` - * and `offsetLeft` (numbers which default to `0`). Use these to control - * which aspects of `source`'s layout are cloned and how much to offset - * the resulting position of `element`. + * - source (Element | String): The source element (or its ID). + * - options (Object): The position fields to clone. + * + * Clones the position and/or dimensions of `source` onto the element as + * defined by `options`, with an optional offset for the `left` and `top` + * properties. + * + * Note that the element will be positioned exactly like `source` whether or + * not it is part of the same [CSS containing + * block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details). + * + * ##### Options + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    NameDefaultDescription
    setLefttrueClones source's left CSS property onto element.
    setToptrueClones source's top CSS property onto element.
    setWidthtrueClones source's width onto element.
    setHeighttrueClones source's width onto element.
    offsetLeft0Number by which to offset element's left CSS property.
    offsetTop0Number by which to offset element's top CSS property.
    **/ clonePosition: function(element, source) { var options = Object.extend({ From f1f6fca60b636339a0356ad00b34ef763d7eae55 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 17:01:40 +0100 Subject: [PATCH 017/502] doc: Clarified units in Element.cumulativeOffset and added example. --- src/dom/dom.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index ec3631cf3..6806c52e6 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1124,10 +1124,24 @@ Element.Methods = { * Element.cumulativeOffset(@element) -> Array * * Returns the offsets of `element` from the top left corner of the - * document. + * document, in pixels. * * Returns an array in the form of `[leftValue, topValue]`. Also accessible * as properties: `{ left: leftValue, top: topValue }`. + * + * ##### Example + * + * Assuming the div `foo` is at (25,40), then: + * + * var offset = $('foo').cumulativeOffset(); + * offset[0]; + * // -> 25 + * offset[1]; + * // -> 40 + * offset.left; + * // -> 25 + * offset.top; + * // -> 40 **/ cumulativeOffset: function(element) { var valueT = 0, valueL = 0; From adf80ad1b34eb48d006410aa97ff838818e83146 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 17:10:13 +0100 Subject: [PATCH 018/502] doc: Merged/updated old docs for Element.cumulativeScrollOffset, clarified units, added example. --- src/dom/dom.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 6806c52e6..84c144dba 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1232,11 +1232,25 @@ Element.Methods = { /** * Element.cumulativeScrollOffset(@element) -> Array * - * Calculates the cumulative scroll offset of an element in nested - * scrolling containers. + * Calculates the cumulative scroll offset (in pixels) of an element in + * nested scrolling containers. * * Returns an array in the form of `[leftValue, topValue]`. Also accessible * as properties: `{ left: leftValue, top: topValue }`. + * + * ##### Example + * + * Assuming the div `foo` is at scroll offset (0,257), then: + * + * var offset = $('foo').cumulativeOffset(); + * offset[0]; + * // -> 0 + * offset[1]; + * // -> 257 + * offset.left; + * // -> 0 + * offset.top; + * // -> 257 **/ cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; From d10aad7bfad2699e87d9bac3a8df48e71ea4dd20 Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 17:15:15 +0100 Subject: [PATCH 019/502] doc: Merged/updated old docs for Element.descendantOf. --- src/dom/dom.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 84c144dba..d68b2327f 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -922,8 +922,28 @@ Element.Methods = { /** * Element.descendantOf(@element, ancestor) -> Boolean + * - ancestor (Element | String): The element to check against (or its ID). * * Checks if `element` is a descendant of `ancestor`. + * + * ##### Example + * + * Assuming: + * + * language: html + *
    + *
    + *
    + *
    + *
    + * + * Then: + * + * $('homo-sapiens').descendantOf('australopithecus'); + * // -> true + * + * $('homo-erectus').descendantOf('homo-sapiens'); + * // -> false **/ descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); From 859197ca8b65602a4d4766d0c78c6f45742794ce Mon Sep 17 00:00:00 2001 From: tjcrowder Date: Fri, 9 Oct 2009 17:20:39 +0100 Subject: [PATCH 020/502] doc: Merged/updated old docs for Element.descendants. --- src/dom/dom.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index d68b2327f..bbf72a7f4 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -506,8 +506,10 @@ Element.Methods = { /** * Element.descendants(@element) -> [Element...] * - * Collects all of element's descendants and returns them as an array of - * elements. + * Collects all of the element's descendants (its children, their children, + * etc.) and returns them as an array of extended elements. As with all of + * Prototype's DOM traversal methods, only Elements are returned, other + * nodes (text nodes, etc.) are skipped. **/ descendants: function(element) { return Element.select(element, "*"); From 067a0ec364a8722d87ff7676f57c3a2a1883a1cc Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 21 Oct 2009 16:35:43 +0200 Subject: [PATCH 021/502] Update to latest PDoc release. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index fa1187431..147250bd6 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit fa1187431a71d43c7b292feecd2dc5a61a6f630b +Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b From 35ed99ba2e49a207afbb038afd67b20778b61995 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 21 Oct 2009 17:00:17 +0200 Subject: [PATCH 022/502] doc: nitpicking. --- src/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index bbf72a7f4..95fdd6a28 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -865,7 +865,7 @@ Element.Methods = { * Removes all of `element`'s child text nodes that contain *only* * whitespace. Returns `element`. * - * This can be very useful when using standard methods like `nextSibling`, + * This can be very useful when using standard properties like `nextSibling`, * `previousSibling`, `firstChild` or `lastChild` to walk the DOM. Usually * you'd only do that if you are interested in all of the DOM nodes, not * just Elements (since if you just need to traverse the Elements in the From 8783065b8e1fc061dfa4cc39569d6fa82e5e6ff5 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 21 Oct 2009 17:24:09 +0200 Subject: [PATCH 023/502] Cosmetic rewrite of String#startsWith and String#endsWith with performance-related comments. --- src/lang/string.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lang/string.js b/src/lang/string.js index 3c7f23f44..c22885ed5 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -429,7 +429,9 @@ Object.extend(String.prototype, (function() { * Checks if the string starts with `substring`. **/ function startsWith(pattern) { - return !this.lastIndexOf(pattern,0); + // Using `lastIndexOf` instead of `indexOf` because execution time doesn't + // depend on string length. + return this.lastIndexOf(pattern, 0) === 0; } /** @@ -439,7 +441,9 @@ Object.extend(String.prototype, (function() { **/ function endsWith(pattern) { var d = this.length - pattern.length; - return d >= 0 && this.indexOf(pattern,d) === d; + // Using `indexOf` instead of `lastIndexOf` because execution time doesn't + // depend on string length. + return d >= 0 && this.indexOf(pattern, d) === d; } /** From f9c680a9bad9f127e1336c85aa9564ba2d1db159 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 21 Oct 2009 17:58:09 +0200 Subject: [PATCH 024/502] More nitpicking. --- src/lang/string.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/string.js b/src/lang/string.js index c22885ed5..26c3cf22e 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -429,8 +429,8 @@ Object.extend(String.prototype, (function() { * Checks if the string starts with `substring`. **/ function startsWith(pattern) { - // Using `lastIndexOf` instead of `indexOf` because execution time doesn't - // depend on string length. + // We use `lastIndexOf` instead of `indexOf` to avoid tying execution + // time to string length when string doesn't start with pattern. return this.lastIndexOf(pattern, 0) === 0; } @@ -441,8 +441,8 @@ Object.extend(String.prototype, (function() { **/ function endsWith(pattern) { var d = this.length - pattern.length; - // Using `indexOf` instead of `lastIndexOf` because execution time doesn't - // depend on string length. + // We use `indexOf` instead of `lastIndexOf` to avoid tying execution + // time to string length when string doesn't end with pattern. return d >= 0 && this.indexOf(pattern, d) === d; } From 2d3e4232303d81fc1b13e6347b299b43599200fa Mon Sep 17 00:00:00 2001 From: Yaffle Date: Tue, 20 Oct 2009 15:49:54 +0600 Subject: [PATCH 025/502] Add missing semicolons. [#837 state:resolved] --- src/dom/dom.js | 2 +- src/dom/selector.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 95fdd6a28..0f0e247c3 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1930,7 +1930,7 @@ Object.extend(Element, Element.Methods); div = null; -})(document.createElement('div')) +})(document.createElement('div')); /** * Element.extend(element) -> Element diff --git a/src/dom/selector.js b/src/dom/selector.js index 947fd892d..7692a62b2 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -309,7 +309,7 @@ Object.extend(Selector, { while (e && le != e && (/\S/).test(e)) { le = e; for (var i = 0; i Date: Fri, 23 Oct 2009 01:17:53 +0200 Subject: [PATCH 026/502] Add Prototype.Selector object with select, match and filter methods as a wraper around Sizzle.Redefine the whole of Selector API in terms of Prototype.Selector. --- src/dom.js | 1 + src/dom/selector.js | 42 ++++++++++----------------------------- src/dom/sizzle_adapter.js | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 src/dom/sizzle_adapter.js diff --git a/src/dom.js b/src/dom.js index f9ff3ff30..595ce1739 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,6 +21,7 @@ //= require "dom/dom" +//= require "dom/sizzle_adapter" //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/src/dom/selector.js b/src/dom/selector.js index ee7b30828..e0115213c 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,5 +1,3 @@ -//= require - /** section: DOM * class Selector * @@ -7,12 +5,6 @@ * selector. **/ (function() { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); - return elements; - } - window.Selector = Class.create({ /** * new Selector(expression) @@ -33,7 +25,7 @@ * selector. **/ findElements: function(rootElement) { - return extend(Sizzle(this.expression, rootElement || document)); + return Prototype.Selector.select(this.expression); }, /** @@ -42,7 +34,7 @@ * Tests whether a `element` matches the instance's CSS selector. **/ match: function(element) { - return Sizzle.matches(this.expression, [element]).length == 1; + return Prototype.Selector.match(element, this.expression); }, toString: function() { @@ -62,9 +54,7 @@ * * The only nodes returned will be those that match the given CSS selector. **/ - matchElements: function(elements, expression) { - return extend(Sizzle.matches(expression, elements)); - }, + matchElements: Prototype.Selector.filter, /** * Selector.findElement(elements, expression[, index = 0]) -> Element @@ -76,13 +66,13 @@ * Returns the `index`th element overall if `expression` is not given. **/ findElement: function(elements, expression, index) { - if (Object.isUndefined(index)) index = 0; - var selector = new Selector(expression), length = elements.length, matchIndex = 0, i; - + index = index || 0; + var matchIndex = 0, element; // Match each element individually, since Sizzle.matches does not preserve order - for (i = 0; i < length; i++) { - if (selector.match(elements[i]) && index == matchIndex++) { - return Element.extend(elements[i]); + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); } } }, @@ -94,18 +84,8 @@ * (or selectors) specified in `expressions`. **/ findChildElements: function(element, expressions) { - var results = [], exprs = expressions.toArray(); - while (exprs.length) Sizzle(exprs.shift(), element || document, results); - return extend(results); + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); } }); - - /** related to: Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. - **/ - window.$$ = function() { - return Selector.findChildElements(document, $A(arguments)); - } })(); diff --git a/src/dom/sizzle_adapter.js b/src/dom/sizzle_adapter.js new file mode 100644 index 000000000..71ad3ee05 --- /dev/null +++ b/src/dom/sizzle_adapter.js @@ -0,0 +1,37 @@ +//= require + +Prototype.Selector = (function(Sizzle) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; + } + + function select(selector, scope) { + return extend(Sizzle(selector, scope || document)); + } + + function match(element, selector) { + return Sizzle.matches(selector, [element]).length == 1; + } + + function filter(elements, selector) { + return extend(Sizzle.matches(selector, elements)); + } + + return { + select: select, + match: match, + filter: filter + }; +})(Sizzle); + +/** related to: Selector + * $$(expression...) -> [Element...] + * + * Returns all elements in the document that match the provided CSS selectors. +**/ +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; \ No newline at end of file From 2d13d45dc8decc699e3d88ca832e3b56a2be1811 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 01:43:48 +0200 Subject: [PATCH 027/502] Remove dependencies to Selector in favor of Prototype.Selector throughout the library. --- src/dom/dom.js | 25 ++++++++++++++----------- src/dom/event.js | 8 ++++++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 1301bc7f2..65182bf88 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -518,9 +518,10 @@ Element.Methods = { * Checks if `element` matches the given CSS selector. **/ match: function(element, selector) { + element = $(element); if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); + return Prototype.Selector.match(element, selector); + return selector.match(element); }, /** @@ -538,7 +539,7 @@ Element.Methods = { if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); + Prototype.Selector.filter(ancestors, expression)[index || 0]; }, /** @@ -574,7 +575,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Selector.findElement(element.previousSiblings(), expression, index); + return Prototype.Selector.filter(element.previousSiblings(), expression)[index]; } else { return element.recursivelyCollect("previousSibling", index + 1)[index]; } @@ -596,7 +597,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Selector.findElement(element.nextSiblings(), expression, index); + return Prototype.Selector.filter(element.nextSiblings(), expression)[index]; } else { var maximumLength = Object.isNumber(index) ? index + 1 : 1; return element.recursivelyCollect("nextSibling", index + 1)[index]; @@ -605,15 +606,16 @@ Element.Methods = { /** - * Element.select(@element, selector...) -> [Element...] - * - selector (String): A CSS selector. + * Element.select(@element, expression...) -> [Element...] + * - expression (String): A CSS selector. * * Takes an arbitrary number of CSS selectors and returns an array of * descendants of `element` that match any of them. **/ select: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element, args); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); }, /** @@ -624,8 +626,9 @@ Element.Methods = { * selector(s). **/ adjacent: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element.parentNode, args).without(element); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); }, /** diff --git a/src/dom/event.js b/src/dom/event.js index 7980c5242..f94ac8757 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -149,8 +149,12 @@ function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); + while (element) { + if (Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode + } } /** From da3e1e361eea151c253f3028b4fd61bcc856a723 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 04:10:30 +0200 Subject: [PATCH 028/502] Clean-up. --- src/dom/selector.js | 10 ++++++++++ src/dom/sizzle_adapter.js | 17 +++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index e0115213c..b8b033115 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -89,3 +89,13 @@ } }); })(); + +/** related to: Selector + * $$(expression...) -> [Element...] + * + * Returns all elements in the document that match the provided CSS selectors. +**/ +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; \ No newline at end of file diff --git a/src/dom/sizzle_adapter.js b/src/dom/sizzle_adapter.js index 71ad3ee05..26249eb54 100644 --- a/src/dom/sizzle_adapter.js +++ b/src/dom/sizzle_adapter.js @@ -1,4 +1,10 @@ +Prototype._original_sizzle = window.Sizzle; //= require +Prototype.Sizzle = window.Sizzle; + +// Restore globals. +window.Sizzle = Prototype._original_sizzle; +delete Prototype._original_sizzle; Prototype.Selector = (function(Sizzle) { function extend(elements) { @@ -24,14 +30,5 @@ Prototype.Selector = (function(Sizzle) { match: match, filter: filter }; -})(Sizzle); +})(Prototype.Sizzle); -/** related to: Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. -**/ -window.$$ = function() { - var expression = $A(arguments).join(', '); - return Prototype.Selector.select(expression, document); -}; \ No newline at end of file From 3e19f959a2c2a002ad6ab9103004682c431c6c28 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 04:42:05 +0200 Subject: [PATCH 029/502] Fix Selector#findElements. --- src/dom/selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index b8b033115..4b03a2574 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -25,7 +25,7 @@ * selector. **/ findElements: function(rootElement) { - return Prototype.Selector.select(this.expression); + return Prototype.Selector.select(this.expression, rootElement); }, /** From 7762e002cb0dd757b347494c896b6aa520b21c16 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 05:49:40 +0200 Subject: [PATCH 030/502] Reorder repository to allow for custom selector engines to be included instead of Sizzle (the current default). Selector engines belong in the vendor directory and must have a selector_engine.js file. To build a Prototype with your engine of choice just spepcify it at build time using the SELECTOR_ENGINE option. For example, to build a version with NSMatcher: rake dist SELECTOR_ENGINE=nwmatcher. --- .gitmodules | 4 +- Rakefile | 18 +- src/dom.js | 2 +- vendor/nwmatcher/nwmatcher-1.1.1.js | 1441 +++++++++++++++++ vendor/nwmatcher/selector_engine.js | 37 + vendor/sizzle | 1 - .../sizzle/selector_engine.js | 2 +- vendor/sizzle/sizzle | 1 + 8 files changed, 1498 insertions(+), 8 deletions(-) create mode 100644 vendor/nwmatcher/nwmatcher-1.1.1.js create mode 100644 vendor/nwmatcher/selector_engine.js delete mode 160000 vendor/sizzle rename src/dom/sizzle_adapter.js => vendor/sizzle/selector_engine.js (96%) create mode 160000 vendor/sizzle/sizzle diff --git a/.gitmodules b/.gitmodules index 00dca5233..61fd31695 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,6 +11,6 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git -[submodule "vendor/sizzle"] - path = vendor/sizzle +[submodule "vendor/sizzle/sizzle"] + path = vendor/sizzle/sizzle url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index cff99f84d..2b36e0821 100755 --- a/Rakefile +++ b/Rakefile @@ -42,7 +42,7 @@ module PrototypeHelper require_sizzle secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR, SIZZLE_DIR], + :load_path => self.load_path, :source_files => [source], :strip_comments => strip_comments ) @@ -51,6 +51,18 @@ module PrototypeHelper secretary.concatenation.save_to(destination) end + def self.load_path + selector = ENV['SELECTOR_ENGINE'] || 'sizzle' + selector_path = File.join(ROOT_DIR, 'vendor', selector) + if File.exists?(selector_path) + [SRC_DIR, selector_path] + else + puts "\nYou seem to be missing vendor/#{selector}." + puts "Built Prototype using Sizzle instead.\n\n" + [SRC_DIR, SIZZLE_DIR] + end + end + def self.build_doc_for(file) mkdir_p TMP_DIR temp_path = File.join(TMP_DIR, "prototype.temp.js") @@ -83,8 +95,8 @@ module PrototypeHelper end def self.require_sizzle - if !File.exists?(File.join(SIZZLE_DIR, 'sizzle.js')) - exit unless get_submodule("Sizzle", "sizzle") + if !File.exists?(File.join(SIZZLE_DIR, 'sizzle', 'sizzle.js')) + exit unless get_submodule("Sizzle", "sizzle/sizzle") end end diff --git a/src/dom.js b/src/dom.js index 595ce1739..1d9fdf30b 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,7 +21,7 @@ //= require "dom/dom" -//= require "dom/sizzle_adapter" +//= require //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/vendor/nwmatcher/nwmatcher-1.1.1.js b/vendor/nwmatcher/nwmatcher-1.1.1.js new file mode 100644 index 000000000..82bc547ea --- /dev/null +++ b/vendor/nwmatcher/nwmatcher-1.1.1.js @@ -0,0 +1,1441 @@ +/* + * Copyright (C) 2007-2009 Diego Perini + * All rights reserved. + * + * nwmatcher.js - A fast CSS selector engine and matcher + * + * Author: Diego Perini + * Version: 1.1.1 + * Created: 20070722 + * Release: 20090516 + * + * License: + * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE + * Download: + * http://javascript.nwbox.com/NWMatcher/nwmatcher.js + */ + +window.NW || (window.NW = {}); + +NW.Dom = function(global) { + + var version = 'nwmatcher-1.1.1', + + // processing context + base = global.document, + + // script loading context + context = global.document, + + // context root element (HTML) + root = context.documentElement, + + // current DOM viewport/window, also used to + // detect Safari 2.0.x [object AbstractView] + view = base.defaultView || base.parentWindow, + + // cache access to native slice + slice = Array.prototype.slice, + + /* BEGIN FEATURE TESTING */ + + // detect native method in object + // not same scope of isHostObject + isNative = function(object, method) { + return object && method in object && + typeof object[method] != 'string' && + // IE/W3C browsers will return [native code] + // Safari 2.0.x and older will return [function] + (/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/). + test(object[method]); + }, + + // NOTE: NATIVE_XXXXX check for existance of method only + // so through the code read it as "supported", maybe BUGGY + + // detect native getAttribute/hasAttribute methods, + // frameworks extend these to elements, but it seems + // this does not work for XML namespaced attributes, + // used to check both getAttribute/hasAttribute in IE + NATIVE_HAS_ATTRIBUTE = isNative(root, 'hasAttribute'), + + // detect if DOM methods are native in browsers + NATIVE_QSAPI = isNative(context, 'querySelector'), + NATIVE_GEBID = isNative(context, 'getElementById'), + NATIVE_GEBTN = isNative(root, 'getElementsByTagName'), + NATIVE_GEBCN = isNative(root, 'getElementsByClassName'), + + // get name of best children collection property available + // detect Safari 2.0.x different children implementation + NATIVE_CHILDREN = + 'children' in root ? + (view && global !== view ? + 'childNodes' : + 'children') : + 'childNodes', + + // nodeList can be converted by native .slice() + // Opera 9.27 and an id="length" will fold this + NATIVE_SLICE_PROTO = + (function() { + var isBuggy = false, div = context.createElement('div'); + try { + div.innerHTML = '
    '; + root.insertBefore(div, root.firstChild); + isBuggy = !!slice.call(div.childNodes, 0)[0]; + } catch(e) { + } finally { + root.removeChild(div).innerHTML = ''; + } + return isBuggy; + })(), + + // check for Mutation Events, DOMAttrModified should be + // enough to ensure DOMNodeInserted/DOMNodeRemoved exist + NATIVE_MUTATION_EVENTS = root.addEventListener ? + (function() { + var isBuggy, id = root.id, + handler = function() { + root.removeEventListener('DOMAttrModified', handler, false); + NATIVE_MUTATION_EVENTS = true; + root.id = id; + }; + root.addEventListener('DOMAttrModified', handler, false); + // now modify an attribute + root.id = 'nw'; + isBuggy = root.id != 'nw'; + root.id = id; + handler = null; + return isBuggy; + })() : + false, + + // NOTE: BUGGY_XXXXX check both for existance and no known bugs, + // so through the code read it as "not supported", or "undefined" + + BUGGY_GEBID = NATIVE_GEBID ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + root.insertBefore(div, root.firstChild); + isBuggy = !!div.ownerDocument.getElementById('Z'); + root.removeChild(div); + div = null; + return isBuggy; + })() : + true, + + // detect IE gEBTN comment nodes bug + BUGGY_GEBTN = NATIVE_GEBTN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.appendChild(context.createComment('')); + div = div.getElementsByTagName('*')[0]; + isBuggy = !!(div && div.nodeType == 8); + div = null; + return isBuggy; + })() : + true, + + // detect Opera gEBCN second class and/or UTF8 bugs + // test is taken from the jQuery selector test suite + BUGGY_GEBCN = NATIVE_GEBCN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + isBuggy = !div.getElementsByClassName('台北')[0]; + div = null; + return isBuggy; + })() : + true, + + // check Seletor API implementations + BUGGY_QSAPI = NATIVE_QSAPI ? (function() { + var isBuggy, pattern = [], div = context.createElement('div'); + + // WebKit case sensitivity bug with className (when no DOCTYPE) + // https://bugs.webkit.org/show_bug.cgi?id=19047 + div.innerHTML = ''; + if (context.compatMode == 'BackCompat' && div.querySelector('.x') !== null) { + return { 'test': function() { return true; } }; + } + + // check :enabled :disabled bugs with hidden fields (Firefox 3.5 QSA bug) + // http://www.w3.org/TR/html5/interactive-elements.html#selector-enabled + div.innerHTML = ''; + // IE8 throws error with these pseudos + try { + isBuggy = div.querySelectorAll(':enabled').length === 1; + } catch(e) { } + isBuggy && pattern.push(':enabled', ':disabled'); + + // check :link bugs with hyperlinks matching (Firefox/Safari) + div.innerHTML = ''; + div.querySelectorAll(':link').length !== 1 && pattern.push(':link'); + + return pattern.length + ? new RegExp(pattern.join('|')) + : { 'test': function() { return false; } }; + })() : + true, + + /* END FEATURE TESTING */ + + // map of attribute names (in HTML and DOM namespaces) + // many are missing here, or maybe there are too many + // first two lines will cover most real cases anyway + /* + // we do not have to write attributes and + // we have a fixed internal getAttribute + // maybe we can skip this case juggling + Attributes = { + 'class': 'className', 'for': 'htmlFor', + 'classname': 'className', 'htmlfor': 'htmlFor', + 'tabindex': 'tabIndex', 'accesskey': 'accessKey', 'maxlength': 'maxLength', + 'readonly': 'readOnly', 'longdesc': 'longDesc', 'frameborder': 'frameBorder', + 'ismap': 'isMap', 'usemap': 'useMap', 'nohref': 'noHref', 'nowrap': 'noWrap', + 'colspan': 'colSpan', 'rowspan': 'rowSpan', + 'cellpadding': 'cellPadding', 'cellspacing': 'cellSpacing', + 'marginwidth': 'marginWidth', 'marginheight': 'marginHeight' + }, + */ + + // See Niels Leenheer blog http://rakaz.nl/item/css_selector_bugs_case_sensitivity + // + // Each attribute definition includes information about the case-sensitivity of its values. + // http://www.w3.org/TR/html4/types.html#h-6.1 + // + // HTML 4 and XHTML both have some attributes that have pre-defined and limited sets of values. + // http://www.w3.org/TR/xhtml1/#h-4.11 + + // Safari 2.0.x seems to always treat attributes as in Quirks mode + insensitiveMap = /^CSS/i.test(context.compatMode) || (view && global !== view) ? { + // must be trated case insensitive in both HTML and XHTML (Strict ?) + 'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1, + 'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1, + 'face': 1, 'enctype': 1, 'hreflang': 1, 'http-equiv': 1, + 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1, + 'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1 + } : { + // must be treated case insensitive in HTML (Quirks ?) + 'align': 1, 'checked': 1, 'clear': 1, 'compact': 1, 'declare': 1, + 'defer': 1, 'dir': 1, 'disabled': 1, 'frame': 1, 'method': 1, + 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, + 'readonly': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, + 'shape': 1, 'valign': 1, 'valuetype': 1 + }, + + // attribute referencing URI values need special treatment in IE + attributesURI = { + 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, + 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 + }, + + // selection functions returning collections + compiledSelectors = { }, + + // matching functions returning booleans + compiledMatchers = { }, + + // place to add exotic functionalities + Selectors = { + // as a simple example this will check + // for chars not in standard ascii table + // + // 'mySpecialSelector': { + // 'Expression': /\u0080-\uffff/, + // 'Callback': mySelectorCallback + //} + // + // 'mySelectorCallback' will be invoked + // only after passing all other standard + // checks and only if none of them worked + }, + + // trim leading/trailing whitespaces + trim = /^\s+|\s+$/g, + + // nth pseudo selectors + position = /:(nth|of-type)/, + + // ascii extended + ascii = /\x00-\xff/, + + // http://www.w3.org/TR/css3-syntax/#characters + // unicode/ISO 10646 characters 161 and higher + // encoding = '|[\\u00a1-\\uffff]',// correct + // NOTE: Safari 2.0.x crashes with escaped (\\) + // Unicode ranges in regular expressions so we + // use a negated character range class instead + // NOTE: [^\\w\\W] tested as good replacement + encoding = '|[^\\x00-\\xa0]', + + // selector validator discard invalid chars + validator = new RegExp("([-_*\\w]" + encoding + ")"), + + // split comma separated selector groups, exclude commas inside () [] + // example: (#div a, ul > li a) group 1 is (#div a) group 2 is (ul > li a) + group = /(([^,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\]|\\.|\*)+)/g, + + // attribute operators + Operators = { + // ! is not really in the specs + // still unit tests have to pass + '!': "%p!=='%m'", + '=': "%p==='%m'", + '^': "%p.indexOf('%m')==0", + '*': "%p.indexOf('%m')>-1", + // sensitivity handled by compiler + // NOTE: working alternative + // '|': "/%m-/i.test(%p+'-')", + '|': "(%p+'-').indexOf('%m-')==0", + '~': "(' '+%p+' ').indexOf(' %m ')>-1", + // precompile in '%m' string length to optimize + // NOTE: working alternative + // '$': "%p.lastIndexOf('%m')==%p.length-'%m'.length" + '$': "%p.substr(%p.length - '%m'.length) === '%m'" + }, + + // optimization expressions + Optimize = { + ID: new RegExp("#((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + TAG: new RegExp("((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + CLASS: new RegExp("\\.((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + // split last, right most, selector group token + TOKEN: /([^\ \>\+\~\,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\])+/g, + descendants: /[^> \w]/, + siblings: /[^+~\w]/ + }, + + // precompiled Regular Expressions + Patterns = { + // element attribute matcher + attribute: /^\[\s*([-\w]*:?(?:[-\w])+)\s*(?:([!^$*~|]*)?(\=)?\s*(["']*)?([^'"]*?)\4)\s*\](.*)/, + // structural pseudo-classes + spseudos: /^\:(root|empty|nth)?-?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/, + // uistates + dynamic + negation pseudo-classes + dpseudos: /^\:((?:[-\w]|\\.)+)(\(([\x22\x27]*)?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/, + // E > F + children: /^\s*\>\s*(.*)/, + // E + F + adjacent: /^\s*\+\s*(.*)/, + // E ~ F + relative: /^\s*\~\s*(.*)/, + // E F + ancestor: /^(\s+)(.*)/, + // all + all: /^\*(.*)/, + // id + id: new RegExp("^#((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)"), + // tag + tagName: new RegExp("^((?:[-_\\w]" + encoding + "]\\\\.)+)(.*)"), + // class + className: new RegExp("^\\.((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)") + }, + + // current CSS3 grouping of Pseudo-Classes + // they allowed implementing extensions + // and improve error notifications + CSS3PseudoClasses = { + Structural: { + 'root': 0, 'empty': 0, + 'first-child': 0, 'last-child': 0, 'only-child': 0, + 'first-of-type': 0, 'last-of-type': 0, 'only-of-type': 0, + 'first-child-of-type': 0, 'last-child-of-type': 0, 'only-child-of-type': 0, + 'nth-child': 0, 'nth-last-child': 0, 'nth-of-type': 0, 'nth-last-of-type': 0 + // (the 4rd line is not in W3C CSS specs but is an accepted alias of 3nd line) + }, + // originally separated in different pseudo-classes + // we have grouped them to optimize a bit size+speed + // all are going through the same code path (switch) + // the assigned value represent current spec status: + // 0 = CSS3, 1 = CSS2, 2 = maybe implemented + Others: { + //UIElementStates: { + // we group them to optimize + 'checked': 0, 'disabled': 0, 'enabled': 0, 'selected': 1, 'indeterminate': 2, + //}, + //Dynamic: { + 'active': 0, 'focus': 0, 'hover': 0, 'link': 0, 'visited': 0, + //}, + // Target: { + 'target': 0, + //}, + // Language: { + 'lang': 0, + //}, + // Negation: { + 'not': 0, + //}, + // Content: { + // http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors + 'contains': 2 + //} + } + }, + + // conditionals optimizers for the compiler + + // do not change this, it is searched & replaced + ACCEPT_NODE = 'r[X++]=N;continue main;', + + // fix for IE gEBTN('*') returning collection with comment nodes + SKIP_COMMENTS = BUGGY_GEBTN ? 'if(e.nodeType!=1){continue;}' : '', + + // use the textContent or innerText property to check CSS3 :contains + // Safari 2 has a bug with innerText and hidden content, using an + // internal replace on the innerHTML property avoids trashing it + CONTAINS_TEXT = + 'textContent' in root ? + 'e.textContent' : + (function() { + var t = context.createElement('div'); + t.innerHTML = '

    p

    '; + t.style.display = 'none'; + return t.innerText.length > 0 ? + 'e.innerText' : + 'this.stripTags(e.innerHTML)'; + })(), + + // to check extensions have not yet been registered + // @return boolean + IS_EMPTY = + function(object) { + if (object && typeof object == 'object') { + for (var i in object) { return false; } + return true; + } + return false; + }, + + // compile a comma separated group of selector + // @mode boolean true for select, false for match + // @return function (compiled) + compileGroup = + function(selector, source, mode) { + var i = 0, seen = { }, parts, token; + if ((parts = selector.match(group))) { + // for each selector in the group + for ( ; i < parts.length; ++i) { + token = parts[i].replace(trim, ''); + // avoid repeating the same token + // in comma separated group (p, p) + if (!seen[token]) { + seen[token] = true; + // reset element reference after the + // first comma if using select() mode + if (i > 0) { + source += 'e=N;'; + } + // insert corresponding mode function + if (mode) { + source += compileSelector(token, ACCEPT_NODE); + } else { + source += compileSelector(token, 'return true;'); + } + } + } + } + if (mode) { + // for select method + return new Function('c,s,d,h', 'var k,e,r,n,C,N,T,X=0,x=0;main:for(k=0,r=[];e=N=c[k];k++){' + SKIP_COMMENTS + source + '}return r;'); + } else { + // for match method + return new Function('e,s,d,h', 'var n,C,N=e,T,X=0,x=0;' + source + 'return false;'); + } + }, + + // compile a CSS3 string selector into + // ad-hoc javascript matching function + // @return string (to be compiled) + compileSelector = + function(selector, source) { + + var i, a, b, n, k, expr, match, result, status, test, type; + + k = 0; + + while (selector) { + + // *** Universal selector + // * match all (empty block, do not remove) + if ((match = selector.match(Patterns.all))) { + // do nothing, handled in the compiler where + // BUGGY_GEBTN return comment nodes (ex: IE) + } + // *** ID selector + // #Foo Id case sensitive + else if ((match = selector.match(Patterns.id))) { + // document can contain conflicting elements (id/name) + source = 'if((n=e.getAttributeNode("id"))&&n.value=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.getAttribute("id")=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.id=="' + match[1] + '"){' + source + '}'; + } + // *** Type selector + // Foo Tag (case insensitive) + else if ((match = selector.match(Patterns.tagName))) { + // both tagName and nodeName properties may be upper or lower case + // depending on their creation NAMESPACE in createElementNS() + source = 'T=e.nodeName;if(T=="' + match[1].toUpperCase() + '"||T=="' + match[1].toLowerCase() + '"){' + source + '}'; + //source = 'if(e.nodeName=="' + match[1].toUpperCase() + '"){' + source + '}'; + //source = 'if(/' + match[1] + '/i.test(e.nodeName)){' + source + '}'; + } + // *** Class selector + // .Foo Class (case sensitive) + else if ((match = selector.match(Patterns.className))) { + // W3C CSS3 specs: element whose "class" attribute has been assigned a list of whitespace-separated values + // see section 6.4 Class selectors and notes at the bottom; explicitly non-normative in this specification. + //source = 'if((" "+e.className+" ").replace(/\\s+/g," ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + source = 'C=e.className;if(C&&(" "+C+" ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + } + // *** Attribute selector + // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= + // case sensitivity is treated differently depending on the document type (see map) + else if ((match = selector.match(Patterns.attribute))) { + // xml namespaced attribute ? + expr = match[1].split(':'); + expr = expr.length == 2 ? expr[1] : expr[0] + ''; + // check case treatment from insensitiveMap + if (insensitiveMap[expr.toLowerCase()]) { + match[5] = match[5].toLowerCase(); + } + source = 'if(' + + (Operators[(match[2] || match[3])] || 'this.hasAttribute(e,"' + match[1] + '")'). + replace(/\%p/g, 'this.getAttribute(e,"' + match[1] + '")' + + (expr ? '' : '.toLowerCase()')).replace(/\%m/g, match[5]) + + '){' + source + '}'; + } + // *** Adjacent sibling combinator + // E + F (F adiacent sibling of E) + else if ((match = selector.match(Patterns.adjacent))) { + source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}'; + } + // *** General sibling combinator + // E ~ F (F relative sibling of E) + else if ((match = selector.match(Patterns.relative))) { + // previousSibling particularly slow on Gecko based browsers prior to FF3.1 + //source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}'; + k++; + source = + 'var N' + k + '=e;e=e.parentNode.firstChild;' + + 'while(e!=N' + k +'){if(e.nodeType==1){' + source + '}e=e.nextSibling;}'; + } + // *** Child combinator + // E > F (F children of E) + else if ((match = selector.match(Patterns.children))) { + source = 'if((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Descendant combinator + // E F (E ancestor of F) + else if ((match = selector.match(Patterns.ancestor))) { + source = 'while((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Structural pseudo-classes + // :root, :empty, + // :first-child, :last-child, :only-child, + // :first-of-type, :last-of-type, :only-of-type, + // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() + else if ((match = selector.match(Patterns.spseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Structural) { + + switch (match[1]) { + case 'root': + // element root of the document + source = 'if(e===h){' + source + '}'; + break; + case 'empty': + // element that has no children + source = 'if(!e.firstChild){' + source + '}'; + break; + default: + type = match[4] == 'of-type' ? 'OfType' : 'Element'; + + if (match[5]) { + // remove the ( ) grabbed above + match[5] = match[5].replace(/\(|\)/g, ''); + if (match[5] == 'even') { + a = 2; + b = 0; + } else if (match[5] == 'odd') { + a = 2; + b = 1; + } else { + // assumes correct "an+b" format + a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0; + a = a || ((n = match[5].match(/(-?\d{1,})n/)) ? parseInt(n[1], 10) : 0); + b = 0 || ((n = match[5].match(/(-?\d{1,})$/)) ? parseInt(n[1], 10) : 0); + } + + // executed after the count is computed + expr = match[2] == 'last' ? (match[4] ? + '(e==h?1:s.TwinsCount[e.parentNode._cssId][e.nodeName.toLowerCase()])' : + '(e==h?1:s.ChildCount[e.parentNode._cssId])') + '-' + (b - 1) : b; + + test = + b < 0 ? + a <= 1 ? + '<=' + Math.abs(b) : + '%' + a + '===' + (a + b) : + a > Math.abs(b) ? '%' + a + '===' + b : + a === Math.abs(b) ? '%' + a + '===' + 0 : + a === 0 ? '==' + expr : + a < 0 ? '<=' + b : + a > 0 ? '>=' + b : + ''; + + // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) + source = 'if(this.' + match[1] + type + '(e)' + test + '){' + source + '}'; + } else { + // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) + // too much overhead calling functions out of the main loop ? + //source = 'if(this.' + match[2] + type + '(e)){' + source + '}'; + source = (match[4] ? 'T=e.nodeName;' : '') + + 'n=e;while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + (match[2] == 'first' || match[2] == 'last' ? source : + 'n=e;while((n=n.' + (match[2] == 'only' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + source + '}') + + '}'; + } + break; + } + + } + // *** Dynamic pseudo-classes + // CSS3 :not, :contains, :enabled, :disabled, :checked, :target + // CSS2 :active, :focus, :hover (no way yet) + // CSS1 :link, :visited + else if ((match = selector.match(Patterns.dpseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Others) { + + if (match[2]) { + // if the pseudo-class is one with a parameter + // remove round brackets grabbed by expression + match[2] = match[2].replace(/^\((.*)\)$/, '$1'); + } + + switch (match[1]) { + // CSS3 part of structural pseudo-classes + case 'not': + // compile nested selectors, need to escape double quotes characters + // since the string we are inserting into already uses double quotes + source = 'if(!this.match(e, "' + match[2].replace(/\x22/g, '\\"') + '")){' + source +'}'; + break; + // maybe deprecated in latest proposals + case 'contains': + match[2] = match[2].replace(/^["']*|['"]*$/g, ''); + source = 'if(' + CONTAINS_TEXT + '.indexOf("' + match[2] + '")>-1){' + source + '}'; + break; + // CSS3 part of UI element states + case 'checked': + source = 'if("form" in e&&/radio|checkbox/i.test(e.type)&&e.checked===true){' + source + '}'; + break; + case 'enabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===false){' + source + '}'; + break; + case 'disabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===true){' + source + '}'; + break; + case 'selected': + // fix Safari selectedIndex property bug + n = base.getElementsByTagName('select'); + for (i = 0; n[i]; i++) { + n[i].selectedIndex; + } + source = 'if("form" in e&&e.selected===true){' + source + '}'; + break; + // CSS3 target element + case 'target': + n = base.location.hash; + source = 'if(e.id!=""&&e.id=="' + n + '"&&"href" in e){' + source + '}'; + break; + // CSS1 & CSS2 link + case 'link': + source = 'if(this.isLink(e)&&!e.visited){' + source + '}'; + break; + case 'visited': + source = 'if(this.isLink(e)&&!!e.visited){' + source + '}'; + break; + // CSS1 & CSS2 UI States IE & FF3 have native support + // these capabilities may be emulated by event managers + case 'active': + source = 'if("activeElement" in d&&e===d.activeElement){' + source + '}'; + break; + case 'hover': + source = 'if("hoverElement" in d&&e===d.hoverElement){' + source + '}'; + break; + case 'focus': + source = 'if("form" in e&&e===d.activeElement&&typeof d.hasFocus=="function"&&d.hasFocus()===true){' + source + '}'; + break; + default: + break; + } + } + else if (!IS_EMPTY(Selectors)) { + // this is where external extensions are + // invoked if expressions match selectors + status = true; + for (name in Selectors) { + if ((match = selector.match(Selectors[name].Expression))) { + result = Selectors[name].Callback(match, source); + source = result.source; + status |= result.status; + } + } + // if an extension fails to parse the selector + // it must return a false boolean in "status" + if (!status) { + // log error but continue execution, don't throw real exceptions + // because blocking following processes maybe is not a good idea + emit('DOMException: unknown pseudo selector "' + selector + '"'); + return source; + } + } + else { + // see above, log error but continue execution + emit('DOMException: unknown token in selector "' + selector + '"'); + return source; + } + + // ensure "match" is not null or empty since + // we do not throw real DOMExceptions above + selector = match && match[match.length - 1]; + } + + return source; + }, + + // enable/disable notifications + VERBOSE = false, + + // a way to control user notification + emit = + function(message) { + if (VERBOSE) { + if (global.console && global.console.log) { + global.console.log(message); + } else { + if (/exception/i.test(message)) { + global.status = message; + global.defaultStatus = message; + } else { + global.status += message; + } + } + } + }, + + // match element with selector + // @return boolean + match = + function(element, selector) { + // make sure an element node was passed + if (element && element.nodeType == 1) { + if (typeof selector == 'string' && selector.length) { + base = element.ownerDocument; + root = base.documentElement; + // save compiled matchers + if (!compiledMatchers[selector]) { + compiledMatchers[selector] = compileGroup(selector, '', false); + } + // result of compiled matcher + return compiledMatchers[selector].call(this, element, snap, base, root); + } + else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + } + } + return false; + }, + + // select elements matching selector + // version using new Selector API + // @return array + select_qsa = + function (selector, from) { + if (validator.test(selector)) { + if ((!from || from.nodeType == 9) && !BUGGY_QSAPI.test(selector)) { + try { + // use available Selectors API + return toArray((from || context).querySelectorAll(selector)); + } catch(e) { } + } + // fall back to NWMatcher select + return client_api.call(this, selector, from || context); + } + return [ ]; + }, + + lastSelector, + lastContext, + lastSlice, + + // select elements matching selector + // version using cross-browser client API + // @return array + client_api = + function client_api(selector, from) { + + var i = 0, done, elements, node, parts, token; + + // extract context if changed + if (!from || lastContext != from) { + // save passed context + lastContext = from; + // ensure from context is set + from || (from = context); + // reference context ownerDocument and document root (HTML) + root = (base = from.ownerDocument || from).documentElement; + } + + if (lastSelector != selector) { + // process valid selector strings + if (validator.test(selector)) { + // save passed selector + lastSelector = selector; + // get right most selector token + parts = selector.match(Optimize.TOKEN); + // only last slice before :not rules + lastSlice = parts[parts.length - 1].split(':not')[0]; + } else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + return [ ]; + } + } + + // caching enabled ? + if (cachingEnabled) { + snap = base.snapshot; + // valid base context storage + if (snap && !snap.isExpired) { + if (snap.Results[selector] && + snap.Roots[selector] == from) { + return snap.Results[selector]; + } + } else { + setCache(true, base); + snap = base.snapshot; + } + } else { + if (position.test(selector)) { + // need to clear storage + snap = new Snapshot(); + } + } + + // pre-filtering pass allow to scale proportionally with big DOM trees; + // this block can be safely removed, it is a speed booster on big pages + // and still maintain the mandatory "document ordered" result set + + // commas separators are treated + // sequentially to maintain order + if (selector.indexOf(',') < 0) { + + // CLASS optimization + if ((parts = lastSlice.match(Optimize.CLASS)) && + (token = parts[parts.length - 1])) { + elements = byClass(token, from); + if (selector == '.' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } + } else + + // MULTI TAG optimization + if (!Optimize.descendants.test(selector) && + (parts = selector.match(/([-_\w]+)|(>)/g)) && NATIVE_GEBTN) { + if (parts.length > 1) { + elements = byTags(parts, from); + } else { + elements = toArray(from.getElementsByTagName(parts[0])); + } + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else + + // TAG optimization + if ((parts = lastSlice.match(Optimize.TAG)) && + (token = parts[parts.length - 1]) && NATIVE_GEBTN) { + elements = from.getElementsByTagName(token); + if (selector == token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return toArray(elements); + } + } + } else + + // ID optimization + if ((parts = lastSlice.match(Optimize.ID)) && + (token = parts[parts.length - 1]) && from.getElementById) { + elements = [byId(token, from)]; + if (elements[0]) { + if (selector == '#' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else { + //if (selector.length != (selector.lastIndexOf('#' + token) + token.length + 1)) { + // optimize narrowing context + from = elements[0].parentNode; + elements = null; + } + } else { + return [ ]; + } + } + + } + + if (!elements || elements.length === 0) { + + var tag = lastSlice.match(/\b([-_\w]+)\b/); + elements = from.getElementsByTagName('*'); + + if ((parts = lastSlice.match(/\#([-_\w]+)$/)) && selector == '#' + parts[1]) { + while ((node = elements[i++])) { + if (node.id == parts[1]) { + return [node]; + } + } + return [ ]; + } else + + if ((parts = lastSlice.match(/\b([-_\w]+)?(?:(\.[-_\w]+)|(\#[-_\w]+))/))) { + while ((node = elements[i++])) { + if ( + (!parts[1] || node.nodeName == parts[1]) && ( + (!parts[3] || (parts[2] == '#' && node.id == parts[3])) || + (!parts[3] || (parts[2] == '.' && node.className == parts[3])) + )) { + return [node]; + } + } + } else + + elements = toArray(elements); + + } + // end of prefiltering pass + + // save compiled selectors + if (!done && !compiledSelectors[selector]) { + compiledSelectors[selector] = compileGroup(selector, '', true); + } + + if (cachingEnabled) { + // a cached result set for the requested selector + snap.Results[selector] = done ? elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + snap.Roots[selector] = from; + return snap.Results[selector]; + } + + // a fresh result set for the requested selector + return done ? + elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + }, + + // use the new native Selector API if available, + // if missing, use the cross-browser client api + // @return array + select = NATIVE_QSAPI ? + select_qsa : + client_api, + + // element by id + // @return element reference or null + byId = + function(id, from) { + var i = 0, element, names, result; + from || (from = context); + id = id.replace(/\\/g, ''); + if (from.getElementById) { + result = from.getElementById(id); + if (result && id != getAttribute(result, 'id') && from.getElementsByName) { + names = from.getElementsByName(id); + result = null; + while ((element = names[i++])) { + if (element.getAttributeNode('id').value == id) { + result = element; + break; + } + } + } + } else { + result = NW.Dom.select('[id="' + id + '"]', from)[0] || null; + } + return result; + }, + + // elements by tag + // @return nodeList (live) + byTag = + function(tag, from) { + return (from || context).getElementsByTagName(tag || '*'); + }, + + // elements by name + // @return array + byName = + function(name, from) { + return this.select('[name="' + name.replace(/\\/g, '') + '"]', from || context); + }, + + // elements by class + // @return nodeList (native GEBCN) + // @return array (non native GEBCN) + byClass = !BUGGY_GEBCN ? + function(name, from) { + return slice.call(from.getElementsByClassName(name.replace(/\\/g, '')), 0); + } : + function(name, from) { + // context is handled in byTag for non native gEBCN + var i = 0, j = 0, r = [ ], node, + nodes = from.getElementsByTagName('*'), + name = new RegExp("(^|\\s)" + name + "(\\s|$)"); + while ((node = nodes[i++])) { + if (name.test(node.className)) { + r[j++] = node; + } + } + return r; + }, + + // recursively get nested tagNames + // example: for "div" pass ["div"] + // "ul li a" pass ["ul", "li", "a"] + // @c array of tag names combinators + // @f from context or default + // @return array + byTags = + function(c, f) { + var h, i, j, k, n, o, p, + id, e = [f || context], + r = [ ], s = [ ], t = [ ]; + h = 0; + i = 0; + while ((n = c[i++])) { + if (n == '>') { + j = 0; + while ((o = e[j++])) { + k = 0; + r = o[NATIVE_CHILDREN]; + while ((p = r[k++])) { + if (p.nodeName.toLowerCase() == c[i].toLowerCase()) { + s[h++] = p; + } + } + } + i++; + h = 0; + e = s; + s = [ ]; + t = [ ]; + } else { + j= 0; + while ((o = e[j++])) { + k = 0; + r = o.getElementsByTagName(n.replace(trim, '')); + while ((p = r[k++])) { + id = (p._cssId || (p._cssId = ++cssId)); + // discard duplicates + if (!t[id]) { + t[id] = true; + s[h++] = p; + } + } + } + h = 0; + e = s; + s = [ ]; + t = [ ]; + } + } + return e; + }, + + // attribute value + // @type string + getAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.getAttribute(attribute) + ''; + } : + function(element, attribute) { + var node; + // specific URI attributes (parameter 2 to fix IE bug) + if (attributesURI[attribute.toLowerCase()]) { + return element.getAttribute(attribute, 2) + ''; + } + node = element.getAttributeNode(attribute); + return (node && node.value) + ''; + }, + + // attribute presence + // @return boolean + hasAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.hasAttribute(attribute); + } : + function(element, attribute) { + // need to get at AttributeNode first on IE + var node = element.getAttributeNode(attribute); + // use both "specified" & "nodeValue" properties + return !!(node && (node.specified || node.nodeValue)); + }, + + // check if element matches the :link pseudo + isLink = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + return hasAttribute(element,'href') && nodeName == 'a' || nodeName == 'area' || nodeName == 'link'; + }, + + // get best children collection available + // Safari 2.0.x "children" implementation + // differs, taken care by feature testing + // @return nodeList (live) + getChildren = + function(element) { + // childNodes is slower to loop through because it contains text nodes + // empty text nodes could be removed at startup to compensate this a bit + return element[NATIVE_CHILDREN] || element.childNodes; + }, + + // test element to be the only element child in its parent + // @return boolean + firstElement = + function(element) { + while ((element = element.previousSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + lastElement = + function(element) { + while ((element = element.nextSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + onlyElement = + function(element) { + return firstElement(element) && lastElement(element); + }, + + // test element to be the first element of-type in its parent + // @return boolean + firstOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.previousSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the last element of-type in its parent + // @return boolean + lastOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.nextSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the only element of-type in its parent + // @return boolean + onlyOfType = + function(element) { + return firstOfType(element) && lastOfType(element); + }, + + // child position by nodeType + // @return number + nthElement = + function(element) { + var i, j, node, nodes, parent, cache = snap.ChildIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + while ((node = nodes[i++])) { + if (node.nodeType == 1) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + snap.ChildCount[parent._cssId || (parent._cssId = ++cssId)] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // child position by nodeName + // @return number + nthOfType = + function(element) { + var i, j, name, node, nodes, pid, parent, cache = snap.TwinsIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + name = element.nodeName.toLowerCase(); + while ((node = nodes[i++])) { + if (node.nodeName.toLowerCase() == name) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + pid = (parent._cssId || (parent._cssId = ++cssId)); + snap.TwinsCount[pid] || (snap.TwinsCount[pid] = { }); + snap.TwinsCount[pid][name] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // convert nodeList to array + // @return array + toArray = NATIVE_SLICE_PROTO ? + function(list) { + return slice.call(list); + } : + function(list) { + // avoid using the length property of nodeLists + // it may have been overwritten by bad HTML code + var i = 0, array = [ ]; + while ((array[i] = list[i++])) { } + array.length--; + return array; + }, + + // cssId expando on elements, + // used to keep child indexes + // during a selection session + cssId = 1, + + // BEGIN: local context caching system + + // ****************** CACHING ****************** + // keep caching states for each context document + // set manually by using setCache(true, context) + cachingEnabled = NATIVE_MUTATION_EVENTS, + + // indexes/count of elements contained in rootElement + // expired by Mutation Events on DOM tree changes + Snapshot = + function() { + return { + // validation flag, creating it already expired, + // code validation will set it valid first time + isExpired: false, + // count of siblings by nodeType or nodeName + ChildCount: [ ], + TwinsCount: [ ], + // ordinal position by nodeType or nodeName + ChildIndex: [ ], + TwinsIndex: [ ], + // result sets and related root contexts + Results: [ ], + Roots: [ ] + }; + }, + + // local indexes, cleared + // between selection calls + snap = new Snapshot(), + + // enable/disable context caching system + // @d optional document context (iframe, xml document) + // script loading context will be used as default context + setCache = + function(enable, d) { + d || (d = context); + if (!!enable) { + d.snapshot = new Snapshot(); + startMutation(d); + } else { + stopMutation(d); + } + cachingEnabled = !!enable; + }, + + // invoked by mutation events to expire cached parts + mutationWrapper = + function(event) { + var d = event.target.ownerDocument || event.target; + stopMutation(d); + switch (event.type) { + case 'DOMAttrModified': + expireCache(d); + break; + case 'DOMNodeInserted': + expireCache(d); + break; + case 'DOMNodeRemoved': + expireCache(d); + break; + default: + break; + } + }, + + // append mutation events + startMutation = + function(d) { + if (!d.isCaching) { + // FireFox/Opera/Safari/KHTML have support for Mutation Events + d.addEventListener('DOMAttrModified', mutationWrapper, false); + d.addEventListener('DOMNodeInserted', mutationWrapper, false); + d.addEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = true; + } + }, + + // remove mutation events + stopMutation = + function(d) { + if (d.isCaching) { + d.removeEventListener('DOMAttrModified', mutationWrapper, false); + d.removeEventListener('DOMNodeInserted', mutationWrapper, false); + d.removeEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = false; + } + }, + + // expire complete cache + // can be invoked by Mutation Events or + // programmatically by other code/scripts + // document context is mandatory no checks + expireCache = + function(d) { + if (d && d.snapshot) { + d.snapshot.isExpired = true; + } + }; + + // END: local context caching system + + return { + + // for testing purposes ! + compile: + function(selector, mode) { + return compileGroup(selector, '', mode || false).toString(); + }, + + // enable/disable cache + setCache: setCache, + + // forced expire of DOM tree cache + expireCache: expireCache, + + // element match selector, return boolean true/false + match: match, + + // elements matching selector, starting from element + select: select, + + // Safari 2 bug with innerText (gasp!) + // used to strip tags from innerHTML + // shouldn't be public, but needed + stripTags: + function(s) { + return s.replace(/<\/?("[^\"]*"|'[^\']*'|[^>])+>/gi, ''); + }, + + // add selector patterns for user defined callbacks + registerSelector: + function (name, rexp, func) { + if (!Selectors[name]) { + Selectors[name] = { }; + Selectors[name].Expression = rexp; + Selectors[name].Callback = func; + } + }, + + // add or overwrite user defined operators + // TODO: check when overwriting standard operators + registerOperator: + function (symbol, resolver) { + if (!Operators[symbol]) { + Operators[symbol] = resolver; + } + }, + + // retrieve element by id attr + byId: byId, + + // retrieve elements by tag name + byTag: byTag, + + // retrieve elements by name attr + byName: byName, + + // retrieve elements by class name + byClass: byClass, + + // check if element matches the :link pseudo + isLink: isLink, + + // retrieve all children elements + getChildren: getChildren, + + // read the value of the attribute + // as was in the original HTML code + getAttribute: getAttribute, + + // check for the attribute presence + // as was in the original HTML code + hasAttribute: hasAttribute, + + // first child element any type + firstElement: firstElement, + + // last child element any type + lastElement: lastElement, + + // only child element any type + onlyElement: onlyElement, + + // first child element of-type + firstOfType: firstOfType, + + // last child element of-type + lastOfType: lastOfType, + + // only child element of-type + onlyOfType: onlyOfType, + + // nth child element any type + nthElement: nthElement, + + // nth child element of-type + nthOfType: nthOfType, + + // convert nodeList to array + toArray: toArray + + }; + +}(this); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js new file mode 100644 index 000000000..899838d97 --- /dev/null +++ b/vendor/nwmatcher/selector_engine.js @@ -0,0 +1,37 @@ +Prototype._original_nw = window.NW; +//= require +Prototype.NW = window.NW; + +// Restore globals. +window.NW = Prototype._original_nw; +delete Prototype._original_nw; + +Prototype.Selector = (function(NW) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; + } + + function select(selector, scope) { + return extend(NW.select(selector, scope || document)); + } + + function filter(elements, selector) { + var results = [], element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (NW.match(element, selector)) { + results.push(Element.extend(element)) + } + } + return results; + } + + return { + select: select, + match: NW.match, + filter: filter + }; +})(Prototype.NW.Dom); + diff --git a/vendor/sizzle b/vendor/sizzle deleted file mode 160000 index e0f5cbc75..000000000 --- a/vendor/sizzle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0f5cbc75d12aa78f3ef30930414b2f88da7b2b8 diff --git a/src/dom/sizzle_adapter.js b/vendor/sizzle/selector_engine.js similarity index 96% rename from src/dom/sizzle_adapter.js rename to vendor/sizzle/selector_engine.js index 26249eb54..efffb4d8e 100644 --- a/src/dom/sizzle_adapter.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require +//= require Prototype.Sizzle = window.Sizzle; // Restore globals. diff --git a/vendor/sizzle/sizzle b/vendor/sizzle/sizzle new file mode 160000 index 000000000..415e466f7 --- /dev/null +++ b/vendor/sizzle/sizzle @@ -0,0 +1 @@ +Subproject commit 415e466f70e5a53f589161b1f2944e5485007409 From cba5468b09a911310bc150683f3af9d9e4078f13 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 06:38:27 +0200 Subject: [PATCH 031/502] Nitpicking. --- vendor/sizzle/selector_engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index efffb4d8e..d7aeeda6c 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require +//= require "sizzle/sizzle" Prototype.Sizzle = window.Sizzle; // Restore globals. From 17e8064d8a154b14b97a29d91fc7dd849dff8265 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 06:39:59 +0200 Subject: [PATCH 032/502] Add legacy Prototype selector engine. Acessible as Prototype.Legacy. Use SELECTOR_ENGINE=legacy to build. --- vendor/legacy/legacy.js | 803 +++++++++++++++++++++++++++++++ vendor/legacy/selector_engine.js | 17 + 2 files changed, 820 insertions(+) create mode 100644 vendor/legacy/legacy.js create mode 100644 vendor/legacy/selector_engine.js diff --git a/vendor/legacy/legacy.js b/vendor/legacy/legacy.js new file mode 100644 index 000000000..ddfd04e15 --- /dev/null +++ b/vendor/legacy/legacy.js @@ -0,0 +1,803 @@ +/* Portions of the Prototype.Legacy class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +Prototype.Legacy = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + // Some versions of Opera 9.x produce incorrect results when using XPath + // with descendant combinators. + // see: http://opera.remcol.ath.cx/bugs/index.php?action=bug&id=652 + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '
    '; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Prototype.Legacy._div) Prototype.Legacy._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Prototype.Legacy._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Prototype.Legacy.patterns, h = Prototype.Legacy.handlers, + c = Prototype.Legacy.criteria, le, p, m, len = ps.length, name; + + if (Prototype.Legacy._cache[e]) { + this.matcher = Prototype.Legacy._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Prototype.Legacy.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + // Versions of Safari 3 before 3.1.2 treat class names case-insensitively in + // quirks mode. If we detect this behavior, we should use a different + // approach. + Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Prototype.Legacy, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Prototype.Legacy.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Prototype.Legacy.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Prototype.Legacy.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Prototype.Legacy.patterns, + x = Prototype.Legacy.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + // combinators must be listed first + // (and descendant needs to be last combinator) + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + // selectors follow + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + // for Prototype.Legacy.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Prototype.Legacy.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML + // due to node properties being mapped directly to attributes + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x'; + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + // use `typeof` operator to prevent errors + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Prototype.Legacy.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Prototype.Legacy.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Prototype.Legacy.handlers; + + if (root == document) { + // We don't have to deal with orphan nodes. Easy. + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + // In IE, we can check sourceIndex to see if root is attached + // to the document. If not (or if sourceIndex is not present), + // we do it the hard way. + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Prototype.Legacy.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Prototype.Legacy.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Prototype.Legacy.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Prototype.Legacy.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Prototype.Legacy.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Prototype.Legacy.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Prototype.Legacy.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Prototype.Legacy.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Prototype.Legacy.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Prototype.Legacy.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Prototype.Legacy.handlers, selectorType, m; + var exclusions = new Prototype.Legacy(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Prototype.Legacy.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Prototype.Legacy.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Prototype.Legacy.split(expressions.join(',')); + var results = [], h = Prototype.Legacy.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Prototype.Legacy(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Prototype.Legacy.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js new file mode 100644 index 000000000..0ad6a8634 --- /dev/null +++ b/vendor/legacy/selector_engine.js @@ -0,0 +1,17 @@ +//= require "legacy" + +Prototype.Selector = (function(Legacy) { + function select(selector, scope) { + return Legacy.findChildElements(scope || document, [selector]); + } + + function match(element, selector) { + return !!Legacy.findElement([element], selector); + } + + return { + select: select, + match: match, + filter: Legacy.matchElements + }; +})(Prototype.Legacy); From 15c323b9ac8fa22b41330fcc1c851297562b0cb3 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 07:18:52 +0200 Subject: [PATCH 033/502] Include NWMatcher as a submodule. --- .gitmodules | 3 + Rakefile | 7 + vendor/nwmatcher/nwmatcher | 1 + vendor/nwmatcher/nwmatcher-1.1.1.js | 1441 --------------------------- vendor/nwmatcher/selector_engine.js | 10 +- 5 files changed, 13 insertions(+), 1449 deletions(-) create mode 160000 vendor/nwmatcher/nwmatcher delete mode 100644 vendor/nwmatcher/nwmatcher-1.1.1.js diff --git a/.gitmodules b/.gitmodules index 61fd31695..51c3adfaf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "vendor/sizzle/sizzle"] path = vendor/sizzle/sizzle url = git://github.com/jeresig/sizzle.git +[submodule "vendor/nwmatcher/nwmatcher"] + path = vendor/nwmatcher/nwmatcher + url = git://github.com/dperini/nwmatcher.git diff --git a/Rakefile b/Rakefile index 2b36e0821..ff38e7712 100755 --- a/Rakefile +++ b/Rakefile @@ -40,6 +40,7 @@ module PrototypeHelper def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets require_sizzle + require_nwmatcher secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), :load_path => self.load_path, @@ -100,6 +101,12 @@ module PrototypeHelper end end + def self.require_nwmatcher + if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'src', 'nwmatcher.js')) + exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") + end + end + def self.get_submodule(name, path) require_git puts "\nYou seem to be missing #{name}. Obtaining it via git...\n\n" diff --git a/vendor/nwmatcher/nwmatcher b/vendor/nwmatcher/nwmatcher new file mode 160000 index 000000000..c9f5d5d4f --- /dev/null +++ b/vendor/nwmatcher/nwmatcher @@ -0,0 +1 @@ +Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664 diff --git a/vendor/nwmatcher/nwmatcher-1.1.1.js b/vendor/nwmatcher/nwmatcher-1.1.1.js deleted file mode 100644 index 82bc547ea..000000000 --- a/vendor/nwmatcher/nwmatcher-1.1.1.js +++ /dev/null @@ -1,1441 +0,0 @@ -/* - * Copyright (C) 2007-2009 Diego Perini - * All rights reserved. - * - * nwmatcher.js - A fast CSS selector engine and matcher - * - * Author: Diego Perini - * Version: 1.1.1 - * Created: 20070722 - * Release: 20090516 - * - * License: - * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE - * Download: - * http://javascript.nwbox.com/NWMatcher/nwmatcher.js - */ - -window.NW || (window.NW = {}); - -NW.Dom = function(global) { - - var version = 'nwmatcher-1.1.1', - - // processing context - base = global.document, - - // script loading context - context = global.document, - - // context root element (HTML) - root = context.documentElement, - - // current DOM viewport/window, also used to - // detect Safari 2.0.x [object AbstractView] - view = base.defaultView || base.parentWindow, - - // cache access to native slice - slice = Array.prototype.slice, - - /* BEGIN FEATURE TESTING */ - - // detect native method in object - // not same scope of isHostObject - isNative = function(object, method) { - return object && method in object && - typeof object[method] != 'string' && - // IE/W3C browsers will return [native code] - // Safari 2.0.x and older will return [function] - (/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/). - test(object[method]); - }, - - // NOTE: NATIVE_XXXXX check for existance of method only - // so through the code read it as "supported", maybe BUGGY - - // detect native getAttribute/hasAttribute methods, - // frameworks extend these to elements, but it seems - // this does not work for XML namespaced attributes, - // used to check both getAttribute/hasAttribute in IE - NATIVE_HAS_ATTRIBUTE = isNative(root, 'hasAttribute'), - - // detect if DOM methods are native in browsers - NATIVE_QSAPI = isNative(context, 'querySelector'), - NATIVE_GEBID = isNative(context, 'getElementById'), - NATIVE_GEBTN = isNative(root, 'getElementsByTagName'), - NATIVE_GEBCN = isNative(root, 'getElementsByClassName'), - - // get name of best children collection property available - // detect Safari 2.0.x different children implementation - NATIVE_CHILDREN = - 'children' in root ? - (view && global !== view ? - 'childNodes' : - 'children') : - 'childNodes', - - // nodeList can be converted by native .slice() - // Opera 9.27 and an id="length" will fold this - NATIVE_SLICE_PROTO = - (function() { - var isBuggy = false, div = context.createElement('div'); - try { - div.innerHTML = '
    '; - root.insertBefore(div, root.firstChild); - isBuggy = !!slice.call(div.childNodes, 0)[0]; - } catch(e) { - } finally { - root.removeChild(div).innerHTML = ''; - } - return isBuggy; - })(), - - // check for Mutation Events, DOMAttrModified should be - // enough to ensure DOMNodeInserted/DOMNodeRemoved exist - NATIVE_MUTATION_EVENTS = root.addEventListener ? - (function() { - var isBuggy, id = root.id, - handler = function() { - root.removeEventListener('DOMAttrModified', handler, false); - NATIVE_MUTATION_EVENTS = true; - root.id = id; - }; - root.addEventListener('DOMAttrModified', handler, false); - // now modify an attribute - root.id = 'nw'; - isBuggy = root.id != 'nw'; - root.id = id; - handler = null; - return isBuggy; - })() : - false, - - // NOTE: BUGGY_XXXXX check both for existance and no known bugs, - // so through the code read it as "not supported", or "undefined" - - BUGGY_GEBID = NATIVE_GEBID ? - (function() { - var isBuggy, div = context.createElement('div'); - div.innerHTML = ''; - root.insertBefore(div, root.firstChild); - isBuggy = !!div.ownerDocument.getElementById('Z'); - root.removeChild(div); - div = null; - return isBuggy; - })() : - true, - - // detect IE gEBTN comment nodes bug - BUGGY_GEBTN = NATIVE_GEBTN ? - (function() { - var isBuggy, div = context.createElement('div'); - div.appendChild(context.createComment('')); - div = div.getElementsByTagName('*')[0]; - isBuggy = !!(div && div.nodeType == 8); - div = null; - return isBuggy; - })() : - true, - - // detect Opera gEBCN second class and/or UTF8 bugs - // test is taken from the jQuery selector test suite - BUGGY_GEBCN = NATIVE_GEBCN ? - (function() { - var isBuggy, div = context.createElement('div'); - div.innerHTML = ''; - isBuggy = !div.getElementsByClassName('台北')[0]; - div = null; - return isBuggy; - })() : - true, - - // check Seletor API implementations - BUGGY_QSAPI = NATIVE_QSAPI ? (function() { - var isBuggy, pattern = [], div = context.createElement('div'); - - // WebKit case sensitivity bug with className (when no DOCTYPE) - // https://bugs.webkit.org/show_bug.cgi?id=19047 - div.innerHTML = ''; - if (context.compatMode == 'BackCompat' && div.querySelector('.x') !== null) { - return { 'test': function() { return true; } }; - } - - // check :enabled :disabled bugs with hidden fields (Firefox 3.5 QSA bug) - // http://www.w3.org/TR/html5/interactive-elements.html#selector-enabled - div.innerHTML = ''; - // IE8 throws error with these pseudos - try { - isBuggy = div.querySelectorAll(':enabled').length === 1; - } catch(e) { } - isBuggy && pattern.push(':enabled', ':disabled'); - - // check :link bugs with hyperlinks matching (Firefox/Safari) - div.innerHTML = ''; - div.querySelectorAll(':link').length !== 1 && pattern.push(':link'); - - return pattern.length - ? new RegExp(pattern.join('|')) - : { 'test': function() { return false; } }; - })() : - true, - - /* END FEATURE TESTING */ - - // map of attribute names (in HTML and DOM namespaces) - // many are missing here, or maybe there are too many - // first two lines will cover most real cases anyway - /* - // we do not have to write attributes and - // we have a fixed internal getAttribute - // maybe we can skip this case juggling - Attributes = { - 'class': 'className', 'for': 'htmlFor', - 'classname': 'className', 'htmlfor': 'htmlFor', - 'tabindex': 'tabIndex', 'accesskey': 'accessKey', 'maxlength': 'maxLength', - 'readonly': 'readOnly', 'longdesc': 'longDesc', 'frameborder': 'frameBorder', - 'ismap': 'isMap', 'usemap': 'useMap', 'nohref': 'noHref', 'nowrap': 'noWrap', - 'colspan': 'colSpan', 'rowspan': 'rowSpan', - 'cellpadding': 'cellPadding', 'cellspacing': 'cellSpacing', - 'marginwidth': 'marginWidth', 'marginheight': 'marginHeight' - }, - */ - - // See Niels Leenheer blog http://rakaz.nl/item/css_selector_bugs_case_sensitivity - // - // Each attribute definition includes information about the case-sensitivity of its values. - // http://www.w3.org/TR/html4/types.html#h-6.1 - // - // HTML 4 and XHTML both have some attributes that have pre-defined and limited sets of values. - // http://www.w3.org/TR/xhtml1/#h-4.11 - - // Safari 2.0.x seems to always treat attributes as in Quirks mode - insensitiveMap = /^CSS/i.test(context.compatMode) || (view && global !== view) ? { - // must be trated case insensitive in both HTML and XHTML (Strict ?) - 'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1, - 'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1, - 'face': 1, 'enctype': 1, 'hreflang': 1, 'http-equiv': 1, - 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1, - 'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1 - } : { - // must be treated case insensitive in HTML (Quirks ?) - 'align': 1, 'checked': 1, 'clear': 1, 'compact': 1, 'declare': 1, - 'defer': 1, 'dir': 1, 'disabled': 1, 'frame': 1, 'method': 1, - 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, - 'readonly': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, - 'shape': 1, 'valign': 1, 'valuetype': 1 - }, - - // attribute referencing URI values need special treatment in IE - attributesURI = { - 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, - 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 - }, - - // selection functions returning collections - compiledSelectors = { }, - - // matching functions returning booleans - compiledMatchers = { }, - - // place to add exotic functionalities - Selectors = { - // as a simple example this will check - // for chars not in standard ascii table - // - // 'mySpecialSelector': { - // 'Expression': /\u0080-\uffff/, - // 'Callback': mySelectorCallback - //} - // - // 'mySelectorCallback' will be invoked - // only after passing all other standard - // checks and only if none of them worked - }, - - // trim leading/trailing whitespaces - trim = /^\s+|\s+$/g, - - // nth pseudo selectors - position = /:(nth|of-type)/, - - // ascii extended - ascii = /\x00-\xff/, - - // http://www.w3.org/TR/css3-syntax/#characters - // unicode/ISO 10646 characters 161 and higher - // encoding = '|[\\u00a1-\\uffff]',// correct - // NOTE: Safari 2.0.x crashes with escaped (\\) - // Unicode ranges in regular expressions so we - // use a negated character range class instead - // NOTE: [^\\w\\W] tested as good replacement - encoding = '|[^\\x00-\\xa0]', - - // selector validator discard invalid chars - validator = new RegExp("([-_*\\w]" + encoding + ")"), - - // split comma separated selector groups, exclude commas inside () [] - // example: (#div a, ul > li a) group 1 is (#div a) group 2 is (ul > li a) - group = /(([^,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\]|\\.|\*)+)/g, - - // attribute operators - Operators = { - // ! is not really in the specs - // still unit tests have to pass - '!': "%p!=='%m'", - '=': "%p==='%m'", - '^': "%p.indexOf('%m')==0", - '*': "%p.indexOf('%m')>-1", - // sensitivity handled by compiler - // NOTE: working alternative - // '|': "/%m-/i.test(%p+'-')", - '|': "(%p+'-').indexOf('%m-')==0", - '~': "(' '+%p+' ').indexOf(' %m ')>-1", - // precompile in '%m' string length to optimize - // NOTE: working alternative - // '$': "%p.lastIndexOf('%m')==%p.length-'%m'.length" - '$': "%p.substr(%p.length - '%m'.length) === '%m'" - }, - - // optimization expressions - Optimize = { - ID: new RegExp("#((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - TAG: new RegExp("((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - CLASS: new RegExp("\\.((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - // split last, right most, selector group token - TOKEN: /([^\ \>\+\~\,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\])+/g, - descendants: /[^> \w]/, - siblings: /[^+~\w]/ - }, - - // precompiled Regular Expressions - Patterns = { - // element attribute matcher - attribute: /^\[\s*([-\w]*:?(?:[-\w])+)\s*(?:([!^$*~|]*)?(\=)?\s*(["']*)?([^'"]*?)\4)\s*\](.*)/, - // structural pseudo-classes - spseudos: /^\:(root|empty|nth)?-?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/, - // uistates + dynamic + negation pseudo-classes - dpseudos: /^\:((?:[-\w]|\\.)+)(\(([\x22\x27]*)?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/, - // E > F - children: /^\s*\>\s*(.*)/, - // E + F - adjacent: /^\s*\+\s*(.*)/, - // E ~ F - relative: /^\s*\~\s*(.*)/, - // E F - ancestor: /^(\s+)(.*)/, - // all - all: /^\*(.*)/, - // id - id: new RegExp("^#((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)"), - // tag - tagName: new RegExp("^((?:[-_\\w]" + encoding + "]\\\\.)+)(.*)"), - // class - className: new RegExp("^\\.((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)") - }, - - // current CSS3 grouping of Pseudo-Classes - // they allowed implementing extensions - // and improve error notifications - CSS3PseudoClasses = { - Structural: { - 'root': 0, 'empty': 0, - 'first-child': 0, 'last-child': 0, 'only-child': 0, - 'first-of-type': 0, 'last-of-type': 0, 'only-of-type': 0, - 'first-child-of-type': 0, 'last-child-of-type': 0, 'only-child-of-type': 0, - 'nth-child': 0, 'nth-last-child': 0, 'nth-of-type': 0, 'nth-last-of-type': 0 - // (the 4rd line is not in W3C CSS specs but is an accepted alias of 3nd line) - }, - // originally separated in different pseudo-classes - // we have grouped them to optimize a bit size+speed - // all are going through the same code path (switch) - // the assigned value represent current spec status: - // 0 = CSS3, 1 = CSS2, 2 = maybe implemented - Others: { - //UIElementStates: { - // we group them to optimize - 'checked': 0, 'disabled': 0, 'enabled': 0, 'selected': 1, 'indeterminate': 2, - //}, - //Dynamic: { - 'active': 0, 'focus': 0, 'hover': 0, 'link': 0, 'visited': 0, - //}, - // Target: { - 'target': 0, - //}, - // Language: { - 'lang': 0, - //}, - // Negation: { - 'not': 0, - //}, - // Content: { - // http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors - 'contains': 2 - //} - } - }, - - // conditionals optimizers for the compiler - - // do not change this, it is searched & replaced - ACCEPT_NODE = 'r[X++]=N;continue main;', - - // fix for IE gEBTN('*') returning collection with comment nodes - SKIP_COMMENTS = BUGGY_GEBTN ? 'if(e.nodeType!=1){continue;}' : '', - - // use the textContent or innerText property to check CSS3 :contains - // Safari 2 has a bug with innerText and hidden content, using an - // internal replace on the innerHTML property avoids trashing it - CONTAINS_TEXT = - 'textContent' in root ? - 'e.textContent' : - (function() { - var t = context.createElement('div'); - t.innerHTML = '

    p

    '; - t.style.display = 'none'; - return t.innerText.length > 0 ? - 'e.innerText' : - 'this.stripTags(e.innerHTML)'; - })(), - - // to check extensions have not yet been registered - // @return boolean - IS_EMPTY = - function(object) { - if (object && typeof object == 'object') { - for (var i in object) { return false; } - return true; - } - return false; - }, - - // compile a comma separated group of selector - // @mode boolean true for select, false for match - // @return function (compiled) - compileGroup = - function(selector, source, mode) { - var i = 0, seen = { }, parts, token; - if ((parts = selector.match(group))) { - // for each selector in the group - for ( ; i < parts.length; ++i) { - token = parts[i].replace(trim, ''); - // avoid repeating the same token - // in comma separated group (p, p) - if (!seen[token]) { - seen[token] = true; - // reset element reference after the - // first comma if using select() mode - if (i > 0) { - source += 'e=N;'; - } - // insert corresponding mode function - if (mode) { - source += compileSelector(token, ACCEPT_NODE); - } else { - source += compileSelector(token, 'return true;'); - } - } - } - } - if (mode) { - // for select method - return new Function('c,s,d,h', 'var k,e,r,n,C,N,T,X=0,x=0;main:for(k=0,r=[];e=N=c[k];k++){' + SKIP_COMMENTS + source + '}return r;'); - } else { - // for match method - return new Function('e,s,d,h', 'var n,C,N=e,T,X=0,x=0;' + source + 'return false;'); - } - }, - - // compile a CSS3 string selector into - // ad-hoc javascript matching function - // @return string (to be compiled) - compileSelector = - function(selector, source) { - - var i, a, b, n, k, expr, match, result, status, test, type; - - k = 0; - - while (selector) { - - // *** Universal selector - // * match all (empty block, do not remove) - if ((match = selector.match(Patterns.all))) { - // do nothing, handled in the compiler where - // BUGGY_GEBTN return comment nodes (ex: IE) - } - // *** ID selector - // #Foo Id case sensitive - else if ((match = selector.match(Patterns.id))) { - // document can contain conflicting elements (id/name) - source = 'if((n=e.getAttributeNode("id"))&&n.value=="' + match[1] + '"){' + source + '}'; - //source = 'if(e.getAttribute("id")=="' + match[1] + '"){' + source + '}'; - //source = 'if(e.id=="' + match[1] + '"){' + source + '}'; - } - // *** Type selector - // Foo Tag (case insensitive) - else if ((match = selector.match(Patterns.tagName))) { - // both tagName and nodeName properties may be upper or lower case - // depending on their creation NAMESPACE in createElementNS() - source = 'T=e.nodeName;if(T=="' + match[1].toUpperCase() + '"||T=="' + match[1].toLowerCase() + '"){' + source + '}'; - //source = 'if(e.nodeName=="' + match[1].toUpperCase() + '"){' + source + '}'; - //source = 'if(/' + match[1] + '/i.test(e.nodeName)){' + source + '}'; - } - // *** Class selector - // .Foo Class (case sensitive) - else if ((match = selector.match(Patterns.className))) { - // W3C CSS3 specs: element whose "class" attribute has been assigned a list of whitespace-separated values - // see section 6.4 Class selectors and notes at the bottom; explicitly non-normative in this specification. - //source = 'if((" "+e.className+" ").replace(/\\s+/g," ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; - source = 'C=e.className;if(C&&(" "+C+" ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; - } - // *** Attribute selector - // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= - // case sensitivity is treated differently depending on the document type (see map) - else if ((match = selector.match(Patterns.attribute))) { - // xml namespaced attribute ? - expr = match[1].split(':'); - expr = expr.length == 2 ? expr[1] : expr[0] + ''; - // check case treatment from insensitiveMap - if (insensitiveMap[expr.toLowerCase()]) { - match[5] = match[5].toLowerCase(); - } - source = 'if(' + - (Operators[(match[2] || match[3])] || 'this.hasAttribute(e,"' + match[1] + '")'). - replace(/\%p/g, 'this.getAttribute(e,"' + match[1] + '")' + - (expr ? '' : '.toLowerCase()')).replace(/\%m/g, match[5]) + - '){' + source + '}'; - } - // *** Adjacent sibling combinator - // E + F (F adiacent sibling of E) - else if ((match = selector.match(Patterns.adjacent))) { - source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}'; - } - // *** General sibling combinator - // E ~ F (F relative sibling of E) - else if ((match = selector.match(Patterns.relative))) { - // previousSibling particularly slow on Gecko based browsers prior to FF3.1 - //source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}'; - k++; - source = - 'var N' + k + '=e;e=e.parentNode.firstChild;' + - 'while(e!=N' + k +'){if(e.nodeType==1){' + source + '}e=e.nextSibling;}'; - } - // *** Child combinator - // E > F (F children of E) - else if ((match = selector.match(Patterns.children))) { - source = 'if((e=e.parentNode)&&e.nodeType==1){' + source + '}'; - } - // *** Descendant combinator - // E F (E ancestor of F) - else if ((match = selector.match(Patterns.ancestor))) { - source = 'while((e=e.parentNode)&&e.nodeType==1){' + source + '}'; - } - // *** Structural pseudo-classes - // :root, :empty, - // :first-child, :last-child, :only-child, - // :first-of-type, :last-of-type, :only-of-type, - // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() - else if ((match = selector.match(Patterns.spseudos)) && - selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Structural) { - - switch (match[1]) { - case 'root': - // element root of the document - source = 'if(e===h){' + source + '}'; - break; - case 'empty': - // element that has no children - source = 'if(!e.firstChild){' + source + '}'; - break; - default: - type = match[4] == 'of-type' ? 'OfType' : 'Element'; - - if (match[5]) { - // remove the ( ) grabbed above - match[5] = match[5].replace(/\(|\)/g, ''); - if (match[5] == 'even') { - a = 2; - b = 0; - } else if (match[5] == 'odd') { - a = 2; - b = 1; - } else { - // assumes correct "an+b" format - a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0; - a = a || ((n = match[5].match(/(-?\d{1,})n/)) ? parseInt(n[1], 10) : 0); - b = 0 || ((n = match[5].match(/(-?\d{1,})$/)) ? parseInt(n[1], 10) : 0); - } - - // executed after the count is computed - expr = match[2] == 'last' ? (match[4] ? - '(e==h?1:s.TwinsCount[e.parentNode._cssId][e.nodeName.toLowerCase()])' : - '(e==h?1:s.ChildCount[e.parentNode._cssId])') + '-' + (b - 1) : b; - - test = - b < 0 ? - a <= 1 ? - '<=' + Math.abs(b) : - '%' + a + '===' + (a + b) : - a > Math.abs(b) ? '%' + a + '===' + b : - a === Math.abs(b) ? '%' + a + '===' + 0 : - a === 0 ? '==' + expr : - a < 0 ? '<=' + b : - a > 0 ? '>=' + b : - ''; - - // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) - source = 'if(this.' + match[1] + type + '(e)' + test + '){' + source + '}'; - } else { - // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) - // too much overhead calling functions out of the main loop ? - //source = 'if(this.' + match[2] + type + '(e)){' + source + '}'; - source = (match[4] ? 'T=e.nodeName;' : '') + - 'n=e;while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' + - 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + - 'if(!n){' + (match[2] == 'first' || match[2] == 'last' ? source : - 'n=e;while((n=n.' + (match[2] == 'only' ? 'previous' : 'next') + 'Sibling)&&' + - 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + - 'if(!n){' + source + '}') + - '}'; - } - break; - } - - } - // *** Dynamic pseudo-classes - // CSS3 :not, :contains, :enabled, :disabled, :checked, :target - // CSS2 :active, :focus, :hover (no way yet) - // CSS1 :link, :visited - else if ((match = selector.match(Patterns.dpseudos)) && - selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Others) { - - if (match[2]) { - // if the pseudo-class is one with a parameter - // remove round brackets grabbed by expression - match[2] = match[2].replace(/^\((.*)\)$/, '$1'); - } - - switch (match[1]) { - // CSS3 part of structural pseudo-classes - case 'not': - // compile nested selectors, need to escape double quotes characters - // since the string we are inserting into already uses double quotes - source = 'if(!this.match(e, "' + match[2].replace(/\x22/g, '\\"') + '")){' + source +'}'; - break; - // maybe deprecated in latest proposals - case 'contains': - match[2] = match[2].replace(/^["']*|['"]*$/g, ''); - source = 'if(' + CONTAINS_TEXT + '.indexOf("' + match[2] + '")>-1){' + source + '}'; - break; - // CSS3 part of UI element states - case 'checked': - source = 'if("form" in e&&/radio|checkbox/i.test(e.type)&&e.checked===true){' + source + '}'; - break; - case 'enabled': - // does not consider hidden input fields - source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===false){' + source + '}'; - break; - case 'disabled': - // does not consider hidden input fields - source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===true){' + source + '}'; - break; - case 'selected': - // fix Safari selectedIndex property bug - n = base.getElementsByTagName('select'); - for (i = 0; n[i]; i++) { - n[i].selectedIndex; - } - source = 'if("form" in e&&e.selected===true){' + source + '}'; - break; - // CSS3 target element - case 'target': - n = base.location.hash; - source = 'if(e.id!=""&&e.id=="' + n + '"&&"href" in e){' + source + '}'; - break; - // CSS1 & CSS2 link - case 'link': - source = 'if(this.isLink(e)&&!e.visited){' + source + '}'; - break; - case 'visited': - source = 'if(this.isLink(e)&&!!e.visited){' + source + '}'; - break; - // CSS1 & CSS2 UI States IE & FF3 have native support - // these capabilities may be emulated by event managers - case 'active': - source = 'if("activeElement" in d&&e===d.activeElement){' + source + '}'; - break; - case 'hover': - source = 'if("hoverElement" in d&&e===d.hoverElement){' + source + '}'; - break; - case 'focus': - source = 'if("form" in e&&e===d.activeElement&&typeof d.hasFocus=="function"&&d.hasFocus()===true){' + source + '}'; - break; - default: - break; - } - } - else if (!IS_EMPTY(Selectors)) { - // this is where external extensions are - // invoked if expressions match selectors - status = true; - for (name in Selectors) { - if ((match = selector.match(Selectors[name].Expression))) { - result = Selectors[name].Callback(match, source); - source = result.source; - status |= result.status; - } - } - // if an extension fails to parse the selector - // it must return a false boolean in "status" - if (!status) { - // log error but continue execution, don't throw real exceptions - // because blocking following processes maybe is not a good idea - emit('DOMException: unknown pseudo selector "' + selector + '"'); - return source; - } - } - else { - // see above, log error but continue execution - emit('DOMException: unknown token in selector "' + selector + '"'); - return source; - } - - // ensure "match" is not null or empty since - // we do not throw real DOMExceptions above - selector = match && match[match.length - 1]; - } - - return source; - }, - - // enable/disable notifications - VERBOSE = false, - - // a way to control user notification - emit = - function(message) { - if (VERBOSE) { - if (global.console && global.console.log) { - global.console.log(message); - } else { - if (/exception/i.test(message)) { - global.status = message; - global.defaultStatus = message; - } else { - global.status += message; - } - } - } - }, - - // match element with selector - // @return boolean - match = - function(element, selector) { - // make sure an element node was passed - if (element && element.nodeType == 1) { - if (typeof selector == 'string' && selector.length) { - base = element.ownerDocument; - root = base.documentElement; - // save compiled matchers - if (!compiledMatchers[selector]) { - compiledMatchers[selector] = compileGroup(selector, '', false); - } - // result of compiled matcher - return compiledMatchers[selector].call(this, element, snap, base, root); - } - else { - emit('DOMException: "' + selector + '" is not a valid CSS selector.'); - } - } - return false; - }, - - // select elements matching selector - // version using new Selector API - // @return array - select_qsa = - function (selector, from) { - if (validator.test(selector)) { - if ((!from || from.nodeType == 9) && !BUGGY_QSAPI.test(selector)) { - try { - // use available Selectors API - return toArray((from || context).querySelectorAll(selector)); - } catch(e) { } - } - // fall back to NWMatcher select - return client_api.call(this, selector, from || context); - } - return [ ]; - }, - - lastSelector, - lastContext, - lastSlice, - - // select elements matching selector - // version using cross-browser client API - // @return array - client_api = - function client_api(selector, from) { - - var i = 0, done, elements, node, parts, token; - - // extract context if changed - if (!from || lastContext != from) { - // save passed context - lastContext = from; - // ensure from context is set - from || (from = context); - // reference context ownerDocument and document root (HTML) - root = (base = from.ownerDocument || from).documentElement; - } - - if (lastSelector != selector) { - // process valid selector strings - if (validator.test(selector)) { - // save passed selector - lastSelector = selector; - // get right most selector token - parts = selector.match(Optimize.TOKEN); - // only last slice before :not rules - lastSlice = parts[parts.length - 1].split(':not')[0]; - } else { - emit('DOMException: "' + selector + '" is not a valid CSS selector.'); - return [ ]; - } - } - - // caching enabled ? - if (cachingEnabled) { - snap = base.snapshot; - // valid base context storage - if (snap && !snap.isExpired) { - if (snap.Results[selector] && - snap.Roots[selector] == from) { - return snap.Results[selector]; - } - } else { - setCache(true, base); - snap = base.snapshot; - } - } else { - if (position.test(selector)) { - // need to clear storage - snap = new Snapshot(); - } - } - - // pre-filtering pass allow to scale proportionally with big DOM trees; - // this block can be safely removed, it is a speed booster on big pages - // and still maintain the mandatory "document ordered" result set - - // commas separators are treated - // sequentially to maintain order - if (selector.indexOf(',') < 0) { - - // CLASS optimization - if ((parts = lastSlice.match(Optimize.CLASS)) && - (token = parts[parts.length - 1])) { - elements = byClass(token, from); - if (selector == '.' + token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } - } else - - // MULTI TAG optimization - if (!Optimize.descendants.test(selector) && - (parts = selector.match(/([-_\w]+)|(>)/g)) && NATIVE_GEBTN) { - if (parts.length > 1) { - elements = byTags(parts, from); - } else { - elements = toArray(from.getElementsByTagName(parts[0])); - } - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } else - - // TAG optimization - if ((parts = lastSlice.match(Optimize.TAG)) && - (token = parts[parts.length - 1]) && NATIVE_GEBTN) { - elements = from.getElementsByTagName(token); - if (selector == token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return toArray(elements); - } - } - } else - - // ID optimization - if ((parts = lastSlice.match(Optimize.ID)) && - (token = parts[parts.length - 1]) && from.getElementById) { - elements = [byId(token, from)]; - if (elements[0]) { - if (selector == '#' + token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } else { - //if (selector.length != (selector.lastIndexOf('#' + token) + token.length + 1)) { - // optimize narrowing context - from = elements[0].parentNode; - elements = null; - } - } else { - return [ ]; - } - } - - } - - if (!elements || elements.length === 0) { - - var tag = lastSlice.match(/\b([-_\w]+)\b/); - elements = from.getElementsByTagName('*'); - - if ((parts = lastSlice.match(/\#([-_\w]+)$/)) && selector == '#' + parts[1]) { - while ((node = elements[i++])) { - if (node.id == parts[1]) { - return [node]; - } - } - return [ ]; - } else - - if ((parts = lastSlice.match(/\b([-_\w]+)?(?:(\.[-_\w]+)|(\#[-_\w]+))/))) { - while ((node = elements[i++])) { - if ( - (!parts[1] || node.nodeName == parts[1]) && ( - (!parts[3] || (parts[2] == '#' && node.id == parts[3])) || - (!parts[3] || (parts[2] == '.' && node.className == parts[3])) - )) { - return [node]; - } - } - } else - - elements = toArray(elements); - - } - // end of prefiltering pass - - // save compiled selectors - if (!done && !compiledSelectors[selector]) { - compiledSelectors[selector] = compileGroup(selector, '', true); - } - - if (cachingEnabled) { - // a cached result set for the requested selector - snap.Results[selector] = done ? elements : - compiledSelectors[selector].call(this, elements, snap, base, root); - snap.Roots[selector] = from; - return snap.Results[selector]; - } - - // a fresh result set for the requested selector - return done ? - elements : - compiledSelectors[selector].call(this, elements, snap, base, root); - }, - - // use the new native Selector API if available, - // if missing, use the cross-browser client api - // @return array - select = NATIVE_QSAPI ? - select_qsa : - client_api, - - // element by id - // @return element reference or null - byId = - function(id, from) { - var i = 0, element, names, result; - from || (from = context); - id = id.replace(/\\/g, ''); - if (from.getElementById) { - result = from.getElementById(id); - if (result && id != getAttribute(result, 'id') && from.getElementsByName) { - names = from.getElementsByName(id); - result = null; - while ((element = names[i++])) { - if (element.getAttributeNode('id').value == id) { - result = element; - break; - } - } - } - } else { - result = NW.Dom.select('[id="' + id + '"]', from)[0] || null; - } - return result; - }, - - // elements by tag - // @return nodeList (live) - byTag = - function(tag, from) { - return (from || context).getElementsByTagName(tag || '*'); - }, - - // elements by name - // @return array - byName = - function(name, from) { - return this.select('[name="' + name.replace(/\\/g, '') + '"]', from || context); - }, - - // elements by class - // @return nodeList (native GEBCN) - // @return array (non native GEBCN) - byClass = !BUGGY_GEBCN ? - function(name, from) { - return slice.call(from.getElementsByClassName(name.replace(/\\/g, '')), 0); - } : - function(name, from) { - // context is handled in byTag for non native gEBCN - var i = 0, j = 0, r = [ ], node, - nodes = from.getElementsByTagName('*'), - name = new RegExp("(^|\\s)" + name + "(\\s|$)"); - while ((node = nodes[i++])) { - if (name.test(node.className)) { - r[j++] = node; - } - } - return r; - }, - - // recursively get nested tagNames - // example: for "div" pass ["div"] - // "ul li a" pass ["ul", "li", "a"] - // @c array of tag names combinators - // @f from context or default - // @return array - byTags = - function(c, f) { - var h, i, j, k, n, o, p, - id, e = [f || context], - r = [ ], s = [ ], t = [ ]; - h = 0; - i = 0; - while ((n = c[i++])) { - if (n == '>') { - j = 0; - while ((o = e[j++])) { - k = 0; - r = o[NATIVE_CHILDREN]; - while ((p = r[k++])) { - if (p.nodeName.toLowerCase() == c[i].toLowerCase()) { - s[h++] = p; - } - } - } - i++; - h = 0; - e = s; - s = [ ]; - t = [ ]; - } else { - j= 0; - while ((o = e[j++])) { - k = 0; - r = o.getElementsByTagName(n.replace(trim, '')); - while ((p = r[k++])) { - id = (p._cssId || (p._cssId = ++cssId)); - // discard duplicates - if (!t[id]) { - t[id] = true; - s[h++] = p; - } - } - } - h = 0; - e = s; - s = [ ]; - t = [ ]; - } - } - return e; - }, - - // attribute value - // @type string - getAttribute = NATIVE_HAS_ATTRIBUTE ? - function(element, attribute) { - return element.getAttribute(attribute) + ''; - } : - function(element, attribute) { - var node; - // specific URI attributes (parameter 2 to fix IE bug) - if (attributesURI[attribute.toLowerCase()]) { - return element.getAttribute(attribute, 2) + ''; - } - node = element.getAttributeNode(attribute); - return (node && node.value) + ''; - }, - - // attribute presence - // @return boolean - hasAttribute = NATIVE_HAS_ATTRIBUTE ? - function(element, attribute) { - return element.hasAttribute(attribute); - } : - function(element, attribute) { - // need to get at AttributeNode first on IE - var node = element.getAttributeNode(attribute); - // use both "specified" & "nodeValue" properties - return !!(node && (node.specified || node.nodeValue)); - }, - - // check if element matches the :link pseudo - isLink = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - return hasAttribute(element,'href') && nodeName == 'a' || nodeName == 'area' || nodeName == 'link'; - }, - - // get best children collection available - // Safari 2.0.x "children" implementation - // differs, taken care by feature testing - // @return nodeList (live) - getChildren = - function(element) { - // childNodes is slower to loop through because it contains text nodes - // empty text nodes could be removed at startup to compensate this a bit - return element[NATIVE_CHILDREN] || element.childNodes; - }, - - // test element to be the only element child in its parent - // @return boolean - firstElement = - function(element) { - while ((element = element.previousSibling) && element.nodeType != 1) { } - return !element; - }, - - // test element to be the only element child in its parent - // @return boolean - lastElement = - function(element) { - while ((element = element.nextSibling) && element.nodeType != 1) { } - return !element; - }, - - // test element to be the only element child in its parent - // @return boolean - onlyElement = - function(element) { - return firstElement(element) && lastElement(element); - }, - - // test element to be the first element of-type in its parent - // @return boolean - firstOfType = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - while ((element = element.previousSibling) && element.nodeName.toLowerCase() != nodeName) { } - return !element; - }, - - // test element to be the last element of-type in its parent - // @return boolean - lastOfType = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - while ((element = element.nextSibling) && element.nodeName.toLowerCase() != nodeName) { } - return !element; - }, - - // test element to be the only element of-type in its parent - // @return boolean - onlyOfType = - function(element) { - return firstOfType(element) && lastOfType(element); - }, - - // child position by nodeType - // @return number - nthElement = - function(element) { - var i, j, node, nodes, parent, cache = snap.ChildIndex; - if (!element._cssId || !cache[element._cssId]) { - if ((parent = element.parentNode).nodeType == 1) { - i = 0; - j = 0; - nodes = parent[NATIVE_CHILDREN]; - while ((node = nodes[i++])) { - if (node.nodeType == 1) { - cache[node._cssId || (node._cssId = ++cssId)] = ++j; - } - } - snap.ChildCount[parent._cssId || (parent._cssId = ++cssId)] = j; - } else { - // does not have a parent (ex.: document) - return 0; - } - } - return cache[element._cssId]; - }, - - // child position by nodeName - // @return number - nthOfType = - function(element) { - var i, j, name, node, nodes, pid, parent, cache = snap.TwinsIndex; - if (!element._cssId || !cache[element._cssId]) { - if ((parent = element.parentNode).nodeType == 1) { - i = 0; - j = 0; - nodes = parent[NATIVE_CHILDREN]; - name = element.nodeName.toLowerCase(); - while ((node = nodes[i++])) { - if (node.nodeName.toLowerCase() == name) { - cache[node._cssId || (node._cssId = ++cssId)] = ++j; - } - } - pid = (parent._cssId || (parent._cssId = ++cssId)); - snap.TwinsCount[pid] || (snap.TwinsCount[pid] = { }); - snap.TwinsCount[pid][name] = j; - } else { - // does not have a parent (ex.: document) - return 0; - } - } - return cache[element._cssId]; - }, - - // convert nodeList to array - // @return array - toArray = NATIVE_SLICE_PROTO ? - function(list) { - return slice.call(list); - } : - function(list) { - // avoid using the length property of nodeLists - // it may have been overwritten by bad HTML code - var i = 0, array = [ ]; - while ((array[i] = list[i++])) { } - array.length--; - return array; - }, - - // cssId expando on elements, - // used to keep child indexes - // during a selection session - cssId = 1, - - // BEGIN: local context caching system - - // ****************** CACHING ****************** - // keep caching states for each context document - // set manually by using setCache(true, context) - cachingEnabled = NATIVE_MUTATION_EVENTS, - - // indexes/count of elements contained in rootElement - // expired by Mutation Events on DOM tree changes - Snapshot = - function() { - return { - // validation flag, creating it already expired, - // code validation will set it valid first time - isExpired: false, - // count of siblings by nodeType or nodeName - ChildCount: [ ], - TwinsCount: [ ], - // ordinal position by nodeType or nodeName - ChildIndex: [ ], - TwinsIndex: [ ], - // result sets and related root contexts - Results: [ ], - Roots: [ ] - }; - }, - - // local indexes, cleared - // between selection calls - snap = new Snapshot(), - - // enable/disable context caching system - // @d optional document context (iframe, xml document) - // script loading context will be used as default context - setCache = - function(enable, d) { - d || (d = context); - if (!!enable) { - d.snapshot = new Snapshot(); - startMutation(d); - } else { - stopMutation(d); - } - cachingEnabled = !!enable; - }, - - // invoked by mutation events to expire cached parts - mutationWrapper = - function(event) { - var d = event.target.ownerDocument || event.target; - stopMutation(d); - switch (event.type) { - case 'DOMAttrModified': - expireCache(d); - break; - case 'DOMNodeInserted': - expireCache(d); - break; - case 'DOMNodeRemoved': - expireCache(d); - break; - default: - break; - } - }, - - // append mutation events - startMutation = - function(d) { - if (!d.isCaching) { - // FireFox/Opera/Safari/KHTML have support for Mutation Events - d.addEventListener('DOMAttrModified', mutationWrapper, false); - d.addEventListener('DOMNodeInserted', mutationWrapper, false); - d.addEventListener('DOMNodeRemoved', mutationWrapper, false); - d.isCaching = true; - } - }, - - // remove mutation events - stopMutation = - function(d) { - if (d.isCaching) { - d.removeEventListener('DOMAttrModified', mutationWrapper, false); - d.removeEventListener('DOMNodeInserted', mutationWrapper, false); - d.removeEventListener('DOMNodeRemoved', mutationWrapper, false); - d.isCaching = false; - } - }, - - // expire complete cache - // can be invoked by Mutation Events or - // programmatically by other code/scripts - // document context is mandatory no checks - expireCache = - function(d) { - if (d && d.snapshot) { - d.snapshot.isExpired = true; - } - }; - - // END: local context caching system - - return { - - // for testing purposes ! - compile: - function(selector, mode) { - return compileGroup(selector, '', mode || false).toString(); - }, - - // enable/disable cache - setCache: setCache, - - // forced expire of DOM tree cache - expireCache: expireCache, - - // element match selector, return boolean true/false - match: match, - - // elements matching selector, starting from element - select: select, - - // Safari 2 bug with innerText (gasp!) - // used to strip tags from innerHTML - // shouldn't be public, but needed - stripTags: - function(s) { - return s.replace(/<\/?("[^\"]*"|'[^\']*'|[^>])+>/gi, ''); - }, - - // add selector patterns for user defined callbacks - registerSelector: - function (name, rexp, func) { - if (!Selectors[name]) { - Selectors[name] = { }; - Selectors[name].Expression = rexp; - Selectors[name].Callback = func; - } - }, - - // add or overwrite user defined operators - // TODO: check when overwriting standard operators - registerOperator: - function (symbol, resolver) { - if (!Operators[symbol]) { - Operators[symbol] = resolver; - } - }, - - // retrieve element by id attr - byId: byId, - - // retrieve elements by tag name - byTag: byTag, - - // retrieve elements by name attr - byName: byName, - - // retrieve elements by class name - byClass: byClass, - - // check if element matches the :link pseudo - isLink: isLink, - - // retrieve all children elements - getChildren: getChildren, - - // read the value of the attribute - // as was in the original HTML code - getAttribute: getAttribute, - - // check for the attribute presence - // as was in the original HTML code - hasAttribute: hasAttribute, - - // first child element any type - firstElement: firstElement, - - // last child element any type - lastElement: lastElement, - - // only child element any type - onlyElement: onlyElement, - - // first child element of-type - firstOfType: firstOfType, - - // last child element of-type - lastOfType: lastOfType, - - // only child element of-type - onlyOfType: onlyOfType, - - // nth child element any type - nthElement: nthElement, - - // nth child element of-type - nthOfType: nthOfType, - - // convert nodeList to array - toArray: toArray - - }; - -}(this); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 899838d97..50830cf97 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_nw = window.NW; -//= require +//= require "nwmatcher/src/nwmatcher" Prototype.NW = window.NW; // Restore globals. @@ -7,14 +7,8 @@ window.NW = Prototype._original_nw; delete Prototype._original_nw; Prototype.Selector = (function(NW) { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); - return elements; - } - function select(selector, scope) { - return extend(NW.select(selector, scope || document)); + return NW.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { From ed27b225a51e6f16029c90c20797b115e9d7fa4e Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 07:31:37 +0200 Subject: [PATCH 034/502] Fix Element extension when using the NWMatcher selector engine. (jddalton) --- Rakefile | 2 +- vendor/nwmatcher/selector_engine.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index ff38e7712..3aa2c18cb 100755 --- a/Rakefile +++ b/Rakefile @@ -102,7 +102,7 @@ module PrototypeHelper end def self.require_nwmatcher - if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'src', 'nwmatcher.js')) + if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'nwmatcher', 'src', 'nwmatcher.js')) exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") end end diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 50830cf97..4db802c4d 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -8,7 +8,11 @@ delete Prototype._original_nw; Prototype.Selector = (function(NW) { function select(selector, scope) { - return NW.select(selector, scope || document, null, Element.extend); + var results = []; + NW.select(selector, scope || document, null, function(element) { + results.push(Element.extend(element)); + }); + return results; } function filter(elements, selector) { From bf8e4048055b43ad174278ffb364c9132b6aabe2 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 22 Oct 2009 17:45:26 +0200 Subject: [PATCH 035/502] Make Event.stopObserving return element in all cases. [#810 state:resolved] --- CHANGELOG | 2 ++ src/dom/event.js | 43 ++++++++++++++++------------------------- test/unit/event_test.js | 2 ++ 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 893408365..2d4e5953c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Make `Event.stopObserving` return element in all cases. [#810 state:resolved] (Yaffle, Tobie Langel) + * String#startsWith, String#endsWith performance optimization (Yaffle, Tobie Langel, kangax) * Rewrite String#camelize using String#replace with a replacement function (Phred, John-David Dalton, Samuel Lebeau, kangax) diff --git a/src/dom/event.js b/src/dom/event.js index 7980c5242..0f35d6557 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -617,44 +617,34 @@ function stopObserving(element, eventName, handler) { element = $(element); - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) return element; - - if (eventName && !handler) { - // If an event name is passed without a handler, we stop observing all - // handlers of that type. - var responders = registry.get(eventName); + var registry = Element.retrieve(element, 'prototype_event_registry') + if (!registry) return element; - if (Object.isUndefined(responders)) return element; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); - return element; - } else if (!eventName) { - // If both the event name and the handler are omitted, we stop observing - // _all_ handlers on the element. + if (!eventName) { + // We stop observing all events. + // e.g.: $(element).stopObserving(); registry.each( function(pair) { - var eventName = pair.key, responders = pair.value; - - responders.each( function(r) { - Element.stopObserving(element, eventName, r.handler); - }); + var eventName = pair.key; + stopObserving(element, eventName); }); return element; } var responders = registry.get(eventName); + if (!responders) return element; - // Fail gracefully if there are no responders assigned. - if (!responders) return; + if (!handler) { + // We stop observing all handlers for the given eventName. + // e.g.: $(element).stopObserving('click'); + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } var responder = responders.find( function(r) { return r.handler === handler; }); if (!responder) return element; - var actualEventName = _getDOMEventName(eventName); - if (eventName.include(':')) { // Custom event. if (element.removeEventListener) @@ -665,6 +655,7 @@ } } else { // Ordinary event. + var actualEventName = _getDOMEventName(eventName); if (element.removeEventListener) element.removeEventListener(actualEventName, responder, false); else diff --git a/test/unit/event_test.js b/test/unit/event_test.js index fc6df2561..3e3416c29 100644 --- a/test/unit/event_test.js +++ b/test/unit/event_test.js @@ -184,6 +184,8 @@ new Test.Unit.Runner({ span.observe("test:somethingHappened", observer); this.assertEqual(span, span.stopObserving("test:somethingHappened")); + this.assertEqual(span, span.stopObserving("test:somethingOtherHappened", observer)); + span.observe("test:somethingHappened", observer); this.assertEqual(span, span.stopObserving()); this.assertEqual(span, span.stopObserving()); // assert it again, after there are no observers From af89847a4ff73715058fc453a20eae5358c3470b Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 22 Oct 2009 17:45:26 +0200 Subject: [PATCH 036/502] Make Event.stopObserving return element in all cases. [#810 state:resolved] --- src/dom/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/event.js b/src/dom/event.js index 0f35d6557..0d86e1108 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -617,7 +617,7 @@ function stopObserving(element, eventName, handler) { element = $(element); - var registry = Element.retrieve(element, 'prototype_event_registry') + var registry = Element.retrieve(element, 'prototype_event_registry'); if (!registry) return element; if (!eventName) { From b0159bdba7b90f504bf225887cf03aedfcc00ee4 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 12:53:03 +0200 Subject: [PATCH 037/502] Document Prototype.Selector API. --- src/dom.js | 6 ++++++ src/dom/selector.js | 48 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/dom.js b/src/dom.js index 1d9fdf30b..1fb0187d1 100644 --- a/src/dom.js +++ b/src/dom.js @@ -19,6 +19,12 @@ * **/ +/** section: DOM + * Prototype + * + * The Prototype namespace. + * +**/ //= require "dom/dom" //= require diff --git a/src/dom/selector.js b/src/dom/selector.js index 4b03a2574..94d8f4ca9 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -98,4 +98,50 @@ window.$$ = function() { var expression = $A(arguments).join(', '); return Prototype.Selector.select(expression, document); -}; \ No newline at end of file +}; + +/** + * Prototype.Selector + * + * A namespace that acts as a wrapper around + * the choosen selector engine (Sizzle by default). + * +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.select(expression[, root = document]) -> [Element...] + * - expression (String): A CSS selector. + * - root (Element | document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches `root` for elements that match the provided CSS selector and returns an + * array of extended [[Element]] objects. +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.match(element, expression) -> Boolean + * - element (Element): a DOM element. + * - expression (String): A CSS selector. + * + * Tests whether `element` matches the CSS selector. +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.filter(elements, expression) -> [Element...] + * - elements (Enumerable): a collection of DOM elements. + * - expression (String): A CSS selector. + * + * Filters the given collection of elements with `expression` and returns an + * array of extended [[Element]] objects. + * + * The only nodes returned will be those that match the given CSS selector. +**/ + +// Implementation provided by selector engine. + From f6f6955a71d0e47955cefdd59cf69ad962355028 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 12:58:10 +0200 Subject: [PATCH 038/502] Marked old Selector API as deprecated. --- src/dom/selector.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index 94d8f4ca9..d079f6602 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,4 +1,4 @@ -/** section: DOM +/** deprecated, section: DOM * class Selector * * A class that queries the document for elements that match a given CSS @@ -6,7 +6,7 @@ **/ (function() { window.Selector = Class.create({ - /** + /** deprecated * new Selector(expression) * - expression (String): A CSS selector. * @@ -16,7 +16,7 @@ this.expression = expression.strip(); }, - /** + /** deprecated * Selector#findElements(root) -> [Element...] * - root (Element || document): A "scope" to search within. All results will * be descendants of this node. @@ -28,7 +28,7 @@ return Prototype.Selector.select(this.expression, rootElement); }, - /** + /** deprecated * Selector#match(element) -> Boolean * * Tests whether a `element` matches the instance's CSS selector. @@ -47,7 +47,7 @@ }); Object.extend(Selector, { - /** + /** deprecated * Selector.matchElements(elements, expression) -> [Element...] * * Filters the given collection of elements with `expression`. @@ -56,7 +56,7 @@ **/ matchElements: Prototype.Selector.filter, - /** + /** deprecated * Selector.findElement(elements, expression[, index = 0]) -> Element * Selector.findElement(elements[, index = 0]) -> Element * @@ -77,7 +77,7 @@ } }, - /** + /** deprecated * Selector.findChildElements(element, expressions) -> [Element...] * * Searches beneath `element` for any elements that match the selector From 7f5ce1e6c250d6dfa4a341c68212b3a372c21a00 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 13:17:53 +0200 Subject: [PATCH 039/502] Clean-up NWMatcher proxy. --- vendor/nwmatcher/selector_engine.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 4db802c4d..fea4a0262 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -8,19 +8,19 @@ delete Prototype._original_nw; Prototype.Selector = (function(NW) { function select(selector, scope) { - var results = []; + var results = [], resultsIndex = 0; NW.select(selector, scope || document, null, function(element) { - results.push(Element.extend(element)); + results[resultsIndex++] = Element.extend(element); }); return results; } function filter(elements, selector) { - var results = [], element; + var results = [], resultsIndex = 0, element; for (var i = 0, length = elements.length; i < length; i++) { element = elements[i]; if (NW.match(element, selector)) { - results.push(Element.extend(element)) + results[resultsIndex++] = Element.extend(element); } } return results; From 75aab03ebabaa33a889751e69e8852e6107374fd Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 18:32:07 +0200 Subject: [PATCH 040/502] Repo and Rakefile refactoring. --- .gitmodules | 11 +++--- Rakefile | 45 +++++++++------------- vendor/legacy/{ => repository}/legacy.js | 0 vendor/legacy/selector_engine.js | 2 +- vendor/nwmatcher/{nwmatcher => repository} | 0 vendor/nwmatcher/selector_engine.js | 2 +- vendor/sizzle/{sizzle => repository} | 0 vendor/sizzle/selector_engine.js | 2 +- 8 files changed, 28 insertions(+), 34 deletions(-) rename vendor/legacy/{ => repository}/legacy.js (100%) rename vendor/nwmatcher/{nwmatcher => repository} (100%) rename vendor/sizzle/{sizzle => repository} (100%) diff --git a/.gitmodules b/.gitmodules index 51c3adfaf..ee65a406d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,9 +11,10 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git -[submodule "vendor/sizzle/sizzle"] - path = vendor/sizzle/sizzle - url = git://github.com/jeresig/sizzle.git -[submodule "vendor/nwmatcher/nwmatcher"] - path = vendor/nwmatcher/nwmatcher + +[submodule "vendor/nwmatcher/repository"] + path = vendor/nwmatcher/repository url = git://github.com/dperini/nwmatcher.git +[submodule "vendor/sizzle/repository"] + path = vendor/sizzle/repository + url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index 3aa2c18cb..31e772ef5 100755 --- a/Rakefile +++ b/Rakefile @@ -9,13 +9,13 @@ module PrototypeHelper DOC_DIR = File.join(ROOT_DIR, 'doc') TEMPLATES_DIR = File.join(ROOT_DIR, 'templates') PKG_DIR = File.join(ROOT_DIR, 'pkg') - SIZZLE_DIR = File.join(ROOT_DIR, 'vendor', 'sizzle') TEST_DIR = File.join(ROOT_DIR, 'test') TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] - - %w[sprockets pdoc unittest_js caja_builder sizzle].each do |name| + DEFAULT_SELECTOR_ENGINE = 'sizzle' + + %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') end @@ -39,11 +39,10 @@ module PrototypeHelper def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets - require_sizzle - require_nwmatcher + get_selector_engine(selector) secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => self.load_path, + :load_path => [SRC_DIR, selector_path], :source_files => [source], :strip_comments => strip_comments ) @@ -52,16 +51,12 @@ module PrototypeHelper secretary.concatenation.save_to(destination) end - def self.load_path - selector = ENV['SELECTOR_ENGINE'] || 'sizzle' - selector_path = File.join(ROOT_DIR, 'vendor', selector) - if File.exists?(selector_path) - [SRC_DIR, selector_path] - else - puts "\nYou seem to be missing vendor/#{selector}." - puts "Built Prototype using Sizzle instead.\n\n" - [SRC_DIR, SIZZLE_DIR] - end + def self.selector + ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE + end + + def self.selector_path + File.join(ROOT_DIR, 'vendor', selector) end def self.build_doc_for(file) @@ -95,15 +90,14 @@ module PrototypeHelper require_submodule('CajaBuilder', 'caja_builder') end - def self.require_sizzle - if !File.exists?(File.join(SIZZLE_DIR, 'sizzle', 'sizzle.js')) - exit unless get_submodule("Sizzle", "sizzle/sizzle") - end - end - - def self.require_nwmatcher - if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'nwmatcher', 'src', 'nwmatcher.js')) - exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") + def self.get_selector_engine(name) + file = File.join(ROOT_DIR, 'vendor', name, 'repository') + unless File.exists?(file) + get_submodule('the required selector engine', "#{name}/repository") + unless File.exists?(file) + puts "The selector engine you required isn't available at vendor/#{name}.\n\n" + exit + end end end @@ -113,7 +107,6 @@ module PrototypeHelper Kernel.system("git submodule init") return true if Kernel.system("git submodule update vendor/#{path}") - # If we got this far, something went wrong. puts "\nLooks like it didn't work. Try it manually:\n\n" puts " $ git submodule init" diff --git a/vendor/legacy/legacy.js b/vendor/legacy/repository/legacy.js similarity index 100% rename from vendor/legacy/legacy.js rename to vendor/legacy/repository/legacy.js diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js index 0ad6a8634..cff9dab4e 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy/selector_engine.js @@ -1,4 +1,4 @@ -//= require "legacy" +//= require "repository/legacy" Prototype.Selector = (function(Legacy) { function select(selector, scope) { diff --git a/vendor/nwmatcher/nwmatcher b/vendor/nwmatcher/repository similarity index 100% rename from vendor/nwmatcher/nwmatcher rename to vendor/nwmatcher/repository diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index fea4a0262..9639ae66b 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_nw = window.NW; -//= require "nwmatcher/src/nwmatcher" +//= require "repository/src/nwmatcher" Prototype.NW = window.NW; // Restore globals. diff --git a/vendor/sizzle/sizzle b/vendor/sizzle/repository similarity index 100% rename from vendor/sizzle/sizzle rename to vendor/sizzle/repository diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index d7aeeda6c..bed246f71 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require "sizzle/sizzle" +//= require "repository/sizzle" Prototype.Sizzle = window.Sizzle; // Restore globals. From 5f85799c3fc93d1131da6c71cc26830b18840385 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 19:29:03 +0200 Subject: [PATCH 041/502] Refactor NWMatcher adapter. --- vendor/nwmatcher/selector_engine.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 9639ae66b..2ebeda760 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -6,21 +6,17 @@ Prototype.NW = window.NW; window.NW = Prototype._original_nw; delete Prototype._original_nw; -Prototype.Selector = (function(NW) { +Prototype.Selector = (function(NWDom) { function select(selector, scope) { - var results = [], resultsIndex = 0; - NW.select(selector, scope || document, null, function(element) { - results[resultsIndex++] = Element.extend(element); - }); - return results; + return NWDom.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { - var results = [], resultsIndex = 0, element; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (NW.match(element, selector)) { - results[resultsIndex++] = Element.extend(element); + var results = [], element, i = 0; + while (element = elements[i++]) { + if (NWDom.match(element, selector)) { + Element.extend(element); + results.push(element); } } return results; @@ -28,7 +24,7 @@ Prototype.Selector = (function(NW) { return { select: select, - match: NW.match, + match: NWDom.match, filter: filter }; })(Prototype.NW.Dom); From 83826829a7732c710f47c77ac45d887a72044224 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 24 Oct 2009 16:12:38 +0200 Subject: [PATCH 042/502] Add Prototype.Selector.engine which simply holds a reference to the actual selector engine used. --- vendor/legacy/selector_engine.js | 13 +++++++------ vendor/nwmatcher/selector_engine.js | 25 ++++++++++++------------- vendor/sizzle/selector_engine.js | 27 +++++++++++++-------------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js index cff9dab4e..21d5c6bca 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy/selector_engine.js @@ -1,17 +1,18 @@ //= require "repository/legacy" -Prototype.Selector = (function(Legacy) { +Prototype.Selector = (function(engine) { function select(selector, scope) { - return Legacy.findChildElements(scope || document, [selector]); + return engine.findChildElements(scope || document, [selector]); } function match(element, selector) { - return !!Legacy.findElement([element], selector); + return !!engine.findElement([element], selector); } return { - select: select, - match: match, - filter: Legacy.matchElements + engine: engine, + select: select, + match: match, + filter: engine.matchElements }; })(Prototype.Legacy); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 2ebeda760..5bc8ac4cd 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,20 +1,15 @@ -Prototype._original_nw = window.NW; +Prototype._original_property = window.NW; //= require "repository/src/nwmatcher" -Prototype.NW = window.NW; -// Restore globals. -window.NW = Prototype._original_nw; -delete Prototype._original_nw; - -Prototype.Selector = (function(NWDom) { +Prototype.Selector = (function(engine) { function select(selector, scope) { - return NWDom.select(selector, scope || document, null, Element.extend); + return engine.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { var results = [], element, i = 0; while (element = elements[i++]) { - if (NWDom.match(element, selector)) { + if (engine.match(element, selector)) { Element.extend(element); results.push(element); } @@ -23,9 +18,13 @@ Prototype.Selector = (function(NWDom) { } return { - select: select, - match: NWDom.match, - filter: filter + engine: engine, + select: select, + match: engine.match, + filter: filter }; -})(Prototype.NW.Dom); +})(NW.Dom); +// Restore globals. +window.NW = Prototype._original_property; +delete Prototype._original_property; \ No newline at end of file diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index bed246f71..6980631c7 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,12 +1,7 @@ -Prototype._original_sizzle = window.Sizzle; +Prototype._original_property = window.Sizzle; //= require "repository/sizzle" -Prototype.Sizzle = window.Sizzle; -// Restore globals. -window.Sizzle = Prototype._original_sizzle; -delete Prototype._original_sizzle; - -Prototype.Selector = (function(Sizzle) { +Prototype.Selector = (function(engine) { function extend(elements) { for (var i = 0, length = elements.length; i < length; i++) elements[i] = Element.extend(elements[i]); @@ -14,21 +9,25 @@ Prototype.Selector = (function(Sizzle) { } function select(selector, scope) { - return extend(Sizzle(selector, scope || document)); + return extend(engine(selector, scope || document)); } function match(element, selector) { - return Sizzle.matches(selector, [element]).length == 1; + return engine.matches(selector, [element]).length == 1; } function filter(elements, selector) { - return extend(Sizzle.matches(selector, elements)); + return extend(engine.matches(selector, elements)); } return { - select: select, - match: match, - filter: filter + engine: engine, + select: select, + match: match, + filter: filter }; -})(Prototype.Sizzle); +})(Sizzle); +// Restore globals. +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; From 107f8125254d52b50e3a7a7f5faa62145dc9c264 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 24 Oct 2009 22:17:37 +0200 Subject: [PATCH 043/502] Modify PrototypeHelper.sprocketize to take a hash of options rather than separate arguments. --- Rakefile | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/Rakefile b/Rakefile index 31e772ef5..ad8bea887 100755 --- a/Rakefile +++ b/Rakefile @@ -13,8 +13,7 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] - DEFAULT_SELECTOR_ENGINE = 'sizzle' - + %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') end @@ -37,32 +36,40 @@ module PrototypeHelper exit end - def self.sprocketize(path, source, destination = nil, strip_comments = true) + def self.sprocketize(options = {}) + options = { + :destination => File.join(DIST_DIR, options[:source]), + :strip_comments => true + }.merge(options) + require_sprockets - get_selector_engine(selector) + load_path = [SRC_DIR] + + if selector = options[:selector_engine] + get_selector_engine(selector) + load_path << File.join(ROOT_DIR, 'vendor', selector) + end + secretary = Sprockets::Secretary.new( - :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR, selector_path], - :source_files => [source], - :strip_comments => strip_comments + :root => File.join(ROOT_DIR, options[:path]), + :load_path => load_path, + :source_files => [options[:source]], + :strip_comments => options[:strip_comments] ) - destination = File.join(DIST_DIR, source) unless destination - secretary.concatenation.save_to(destination) - end - - def self.selector - ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE - end - - def self.selector_path - File.join(ROOT_DIR, 'vendor', selector) + secretary.concatenation.save_to(options[:destination]) end def self.build_doc_for(file) mkdir_p TMP_DIR temp_path = File.join(TMP_DIR, "prototype.temp.js") - sprocketize('src', file, temp_path, false) + sprocketize( + :path => 'src', + :source => file, + :destination => temp_path, + :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle', + :strip_comments => false + ) rm_rf DOC_DIR PDoc::Runner.new(temp_path, { @@ -140,7 +147,11 @@ task :default => [:dist, :dist_helper, :package, :clean_package_source] desc "Builds the distribution." task :dist do - PrototypeHelper.sprocketize("src", "prototype.js") + PrototypeHelper.sprocketize( + :path => 'src', + :source => 'prototype.js', + :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle' + ) end namespace :doc do @@ -158,7 +169,7 @@ task :doc => ['doc:build'] desc "Builds the updating helper." task :dist_helper do - PrototypeHelper.sprocketize("ext/update_helper", "prototype_update_helper.js") + PrototypeHelper.sprocketize(:path => 'ext/update_helper', :source => 'prototype_update_helper.js') end Rake::PackageTask.new('prototype', PrototypeHelper::VERSION) do |package| From fdf3424f783f3a1bd1cf8a935204286c463d4886 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 00:40:48 +0200 Subject: [PATCH 044/502] Add unit tests. --- test/unit/fixtures/selector_engine.html | 4 ++ test/unit/selector_engine_test.js | 53 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 test/unit/fixtures/selector_engine.html create mode 100644 test/unit/selector_engine_test.js diff --git a/test/unit/fixtures/selector_engine.html b/test/unit/fixtures/selector_engine.html new file mode 100644 index 000000000..5b1acd24b --- /dev/null +++ b/test/unit/fixtures/selector_engine.html @@ -0,0 +1,4 @@ +
    +
    +
    +
    \ No newline at end of file diff --git a/test/unit/selector_engine_test.js b/test/unit/selector_engine_test.js new file mode 100644 index 000000000..dec966fc1 --- /dev/null +++ b/test/unit/selector_engine_test.js @@ -0,0 +1,53 @@ +/* +
    +
    +
    +
    +*/ + +new Test.Unit.Runner({ + testEngine: function() { + this.assert(Prototype.Selector.engine); + }, + + testSelect: function() { + var elements = Prototype.Selector.select('.test_class'); + + this.assert(Object.isArray(elements)); + this.assertEqual(2, elements.length); + this.assertEqual('test_div_parent', elements[0].id); + this.assertEqual('test_div_child', elements[1].id); + }, + + testSelectWithContext: function() { + var elements = Prototype.Selector.select('.test_class', $('test_div_parent')); + + this.assert(Object.isArray(elements)); + this.assertEqual(1, elements.length); + this.assertEqual('test_div_child', elements[0].id); + }, + + testSelectWithEmptyResult: function() { + var elements = Prototype.Selector.select('.non_existent'); + + this.assert(Object.isArray(elements)); + this.assertEqual(0, elements.length); + }, + + testMatch: function() { + var element = $('test_div_parent'); + + this.assertEqual(true, Prototype.Selector.match(element, '.test_class')); + this.assertEqual(false, Prototype.Selector.match(element, '.non_existent')); + }, + + testFilter: function() { + var elements = document.getElementsByTagName('*'), + filtered = Prototype.Selector.filter(elements, '.test_class'); + + this.assert(Object.isArray(filtered)); + this.assertEqual(2, filtered.length); + this.assertEqual('test_div_parent', filtered[0].id); + this.assertEqual('test_div_child', filtered[1].id); + } +}); \ No newline at end of file From 24569d1d985f74d4d86d46e94d910e777ddfced4 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:13:11 +0100 Subject: [PATCH 045/502] Deprecate the Selector API. --- .../prototype_update_helper.html | 32 +++++++ ext/update_helper/prototype_update_helper.js | 56 +++++++++++ src/deprecated.js | 92 ++++++++++++++++++ src/dom/selector.js | 94 +------------------ 4 files changed, 181 insertions(+), 93 deletions(-) diff --git a/ext/update_helper/prototype_update_helper.html b/ext/update_helper/prototype_update_helper.html index b2d355de6..b5aa69f7c 100644 --- a/ext/update_helper/prototype_update_helper.html +++ b/ext/update_helper/prototype_update_helper.html @@ -280,6 +280,38 @@

    Prototype Unit test file

    this.assertNotNotified(); }, + testSelectorInstanceMethods: function() { + var selector = new Selector('div'); + this.assertWarnNotified('The Selector class has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.findElements(document); + this.assertWarnNotified('Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.match(document.documentElement); + this.assertWarnNotified('Selector#match has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.toString(); + this.assertWarnNotified('Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.inspect(); + this.assertWarnNotified('Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorMatchElements: function() { + Selector.matchElements([], 'div'); + this.assertWarnNotified('Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorFindElement: function() { + Selector.findElement([], 'div'); + this.assertWarnNotified('Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorFindChildElements: function() { + Selector.findChildElements(document, 'div'); + this.assertWarnNotified('Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + testLogDeprecationOption: function() { prototypeUpdateHelper.logLevel = UpdateHelper.Warn; var h = $H({ foo: 2 }); diff --git a/ext/update_helper/prototype_update_helper.js b/ext/update_helper/prototype_update_helper.js index e5107ca29..353deee9f 100644 --- a/ext/update_helper/prototype_update_helper.js +++ b/ext/update_helper/prototype_update_helper.js @@ -275,6 +275,62 @@ var prototypeUpdateHelper = new UpdateHelper([ message: 'The class API has been fully revised and now allows for mixins and inheritance.\n' + 'You can find more about it here: http://prototypejs.org/learn/class-inheritance', condition: function() { return !arguments.length } + }, + + { + methodName: 'initialize', + namespace: Selector.prototype, + message: 'The Selector class has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findElements', + namespace: Selector.prototype, + message: 'Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'match', + namespace: Selector.prototype, + message: 'Selector#match has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'toString', + namespace: Selector.prototype, + message: 'Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'inspect', + namespace: Selector.prototype, + message: 'Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'matchElements', + namespace: Selector, + message: 'Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findElement', + namespace: Selector, + message: 'Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findChildElements', + namespace: Selector, + message: 'Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' } ]); diff --git a/src/deprecated.js b/src/deprecated.js index c5ea33ef7..c008d66fd 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -184,3 +184,95 @@ Element.ClassNames.prototype = { Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ + +/** deprecated, section: DOM + * class Selector + * + * A class that queries the document for elements that match a given CSS + * selector. +**/ +(function() { + window.Selector = Class.create({ + /** deprecated + * new Selector(expression) + * - expression (String): A CSS selector. + * + * Creates a `Selector` with the given CSS selector. + **/ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + /** deprecated + * Selector#findElements(root) -> [Element...] + * - root (Element | document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches the document for elements that match the instance's CSS + * selector. + **/ + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + /** deprecated + * Selector#match(element) -> Boolean + * + * Tests whether a `element` matches the instance's CSS selector. + **/ + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + /** deprecated + * Selector.matchElements(elements, expression) -> [Element...] + * + * Filters the given collection of elements with `expression`. + * + * The only nodes returned will be those that match the given CSS selector. + **/ + matchElements: Prototype.Selector.filter, + + /** deprecated + * Selector.findElement(elements, expression[, index = 0]) -> Element + * Selector.findElement(elements[, index = 0]) -> Element + * + * Returns the `index`th element in the collection that matches + * `expression`. + * + * Returns the `index`th element overall if `expression` is not given. + **/ + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + // Match each element individually, since Sizzle.matches does not preserve order + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + /** deprecated + * Selector.findChildElements(element, expressions) -> [Element...] + * + * Searches beneath `element` for any elements that match the selector + * (or selectors) specified in `expressions`. + **/ + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/src/dom/selector.js b/src/dom/selector.js index d079f6602..e0e5d40d8 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,96 +1,4 @@ -/** deprecated, section: DOM - * class Selector - * - * A class that queries the document for elements that match a given CSS - * selector. -**/ -(function() { - window.Selector = Class.create({ - /** deprecated - * new Selector(expression) - * - expression (String): A CSS selector. - * - * Creates a `Selector` with the given CSS selector. - **/ - initialize: function(expression) { - this.expression = expression.strip(); - }, - - /** deprecated - * Selector#findElements(root) -> [Element...] - * - root (Element || document): A "scope" to search within. All results will - * be descendants of this node. - * - * Searches the document for elements that match the instance's CSS - * selector. - **/ - findElements: function(rootElement) { - return Prototype.Selector.select(this.expression, rootElement); - }, - - /** deprecated - * Selector#match(element) -> Boolean - * - * Tests whether a `element` matches the instance's CSS selector. - **/ - match: function(element) { - return Prototype.Selector.match(element, this.expression); - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Selector, { - /** deprecated - * Selector.matchElements(elements, expression) -> [Element...] - * - * Filters the given collection of elements with `expression`. - * - * The only nodes returned will be those that match the given CSS selector. - **/ - matchElements: Prototype.Selector.filter, - - /** deprecated - * Selector.findElement(elements, expression[, index = 0]) -> Element - * Selector.findElement(elements[, index = 0]) -> Element - * - * Returns the `index`th element in the collection that matches - * `expression`. - * - * Returns the `index`th element overall if `expression` is not given. - **/ - findElement: function(elements, expression, index) { - index = index || 0; - var matchIndex = 0, element; - // Match each element individually, since Sizzle.matches does not preserve order - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (Prototype.Selector.match(element, expression) && index === matchIndex++) { - return Element.extend(element); - } - } - }, - - /** deprecated - * Selector.findChildElements(element, expressions) -> [Element...] - * - * Searches beneath `element` for any elements that match the selector - * (or selectors) specified in `expressions`. - **/ - findChildElements: function(element, expressions) { - var selector = expressions.toArray().join(', '); - return Prototype.Selector.select(selector, element || document); - } - }); -})(); - -/** related to: Selector +/** related to: Prototype.Selector * $$(expression...) -> [Element...] * * Returns all elements in the document that match the provided CSS selectors. From 70c5e98d44ecb7129501155811088a5b26cd1bf7 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:24:51 +0100 Subject: [PATCH 046/502] Make the UpdaterHelper function on WebKit-based browsers. --- ext/update_helper/prototype_update_helper.html | 7 ++++++- ext/update_helper/prototype_update_helper.js | 4 ++-- ext/update_helper/update_helper.js | 11 +++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ext/update_helper/prototype_update_helper.html b/ext/update_helper/prototype_update_helper.html index b5aa69f7c..625911d5d 100644 --- a/ext/update_helper/prototype_update_helper.html +++ b/ext/update_helper/prototype_update_helper.html @@ -81,7 +81,12 @@

    Prototype Unit test file

    new Test.Unit.Runner({ testGetStack: function() { - this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack()); + var stack = prototypeUpdateHelper.getStack(); + if (stack === '') { + this.info('UpdaterHelper#getStack is currently not supported on this browser.') + } else { + this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack()); + } }, testDisplay: function() { diff --git a/ext/update_helper/prototype_update_helper.js b/ext/update_helper/prototype_update_helper.js index 353deee9f..b0229667c 100644 --- a/ext/update_helper/prototype_update_helper.js +++ b/ext/update_helper/prototype_update_helper.js @@ -1,6 +1,6 @@ //= require "update_helper" -/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008 Tobie Langel +/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008-2009 Tobie Langel * * UpdateHelper for Prototype is freely distributable under the same * terms as Prototype (MIT-style license). @@ -17,7 +17,7 @@ * * This, for example, will prevent deprecation messages from being logged. * - * THIS SCRIPT WORKS IN FIREFOX ONLY + * THIS SCRIPT DOES NOT WORK IN INTERNET EXPLORER *--------------------------------------------------------------------------*/ var prototypeUpdateHelper = new UpdateHelper([ diff --git a/ext/update_helper/update_helper.js b/ext/update_helper/update_helper.js index 81a0ad082..2a4b7b6c4 100644 --- a/ext/update_helper/update_helper.js +++ b/ext/update_helper/update_helper.js @@ -1,4 +1,4 @@ -/* Update Helper (c) 2008 Tobie Langel +/* Update Helper (c) 2008-2009 Tobie Langel * * Requires Prototype >= 1.6.0 * @@ -54,9 +54,12 @@ var UpdateHelper = Class.create({ try { throw new Error("stack"); } catch(e) { - return (e.stack || '').match(this.Regexp).reject(function(path) { - return /(prototype|unittest|update_helper)\.js/.test(path); - }).join("\n"); + var match = (e.stack || '').match(this.Regexp); + if (match) { + return match.reject(function(path) { + return (/(prototype|unittest|update_helper)\.js/).test(path); + }).join("\n"); + } else { return ''; } } }, From 74ae0a5537ad040faa972edafa6e0e9df2f9d10c Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:35:59 +0100 Subject: [PATCH 047/502] Renamed Prototype.Legacy to Prototype.LegacySelector by popular request. --- .../repository/legacy_selector.js} | 134 +++++++++--------- .../selector_engine.js | 4 +- 2 files changed, 69 insertions(+), 69 deletions(-) rename vendor/{legacy/repository/legacy.js => legacy_selector/repository/legacy_selector.js} (82%) rename vendor/{legacy => legacy_selector}/selector_engine.js (83%) diff --git a/vendor/legacy/repository/legacy.js b/vendor/legacy_selector/repository/legacy_selector.js similarity index 82% rename from vendor/legacy/repository/legacy.js rename to vendor/legacy_selector/repository/legacy_selector.js index ddfd04e15..0253d4cfe 100644 --- a/vendor/legacy/repository/legacy.js +++ b/vendor/legacy_selector/repository/legacy_selector.js @@ -1,8 +1,8 @@ -/* Portions of the Prototype.Legacy class are derived from Jack Slocum's DomQuery, +/* Portions of the Prototype.LegacySelector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ -Prototype.Legacy = Class.create({ +Prototype.LegacySelector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); @@ -66,14 +66,14 @@ Prototype.Legacy = Class.create({ shouldUseSelectorsAPI: function() { if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - if (Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES) return false; + if (Prototype.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES) return false; - if (!Prototype.Legacy._div) Prototype.Legacy._div = new Element('div'); + if (!Prototype.LegacySelector._div) Prototype.LegacySelector._div = new Element('div'); // Make sure the browser treats the selector as valid. Test on an // isolated element to minimize cost of this check. try { - Prototype.Legacy._div.querySelector(this.expression); + Prototype.LegacySelector._div.querySelector(this.expression); } catch(e) { return false; } @@ -82,16 +82,16 @@ Prototype.Legacy = Class.create({ }, compileMatcher: function() { - var e = this.expression, ps = Prototype.Legacy.patterns, h = Prototype.Legacy.handlers, - c = Prototype.Legacy.criteria, le, p, m, len = ps.length, name; + var e = this.expression, ps = Prototype.LegacySelector.patterns, h = Prototype.LegacySelector.handlers, + c = Prototype.LegacySelector.criteria, le, p, m, len = ps.length, name; - if (Prototype.Legacy._cache[e]) { - this.matcher = Prototype.Legacy._cache[e]; + if (Prototype.LegacySelector._cache[e]) { + this.matcher = Prototype.LegacySelector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Prototype.Legacy.handlers, c = false, n;"]; + "var r = root, h = Prototype.LegacySelector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; @@ -109,15 +109,15 @@ Prototype.Legacy = Class.create({ this.matcher.push("return h.unique(n);\n}"); eval(this.matcher.join('\n')); - Prototype.Legacy._cache[this.expression] = this.matcher; + Prototype.LegacySelector._cache[this.expression] = this.matcher; }, compileXPathMatcher: function() { - var e = this.expression, ps = Prototype.Legacy.patterns, - x = Prototype.Legacy.xpath, le, m, len = ps.length, name; + var e = this.expression, ps = Prototype.LegacySelector.patterns, + x = Prototype.LegacySelector.xpath, le, m, len = ps.length, name; - if (Prototype.Legacy._cache[e]) { - this.xpath = Prototype.Legacy._cache[e]; return; + if (Prototype.LegacySelector._cache[e]) { + this.xpath = Prototype.LegacySelector._cache[e]; return; } this.matcher = ['.//*']; @@ -135,7 +135,7 @@ Prototype.Legacy = Class.create({ } this.xpath = this.matcher.join(''); - Prototype.Legacy._cache[this.expression] = this.xpath; + Prototype.LegacySelector._cache[this.expression] = this.xpath; }, findElements: function(root) { @@ -168,7 +168,7 @@ Prototype.Legacy = Class.create({ match: function(element) { this.tokens = []; - var e = this.expression, ps = Prototype.Legacy.patterns, as = Prototype.Legacy.assertions; + var e = this.expression, ps = Prototype.LegacySelector.patterns, as = Prototype.LegacySelector.assertions; var le, p, m, len = ps.length, name; while (e && le !== e && (/\S/).test(e)) { @@ -177,7 +177,7 @@ Prototype.Legacy = Class.create({ p = ps[i].re; name = ps[i].name; if (m = e.match(p)) { - // use the Prototype.Legacy.assertions methods unless the selector + // use the Prototype.LegacySelector.assertions methods unless the selector // is too complex. if (as[name]) { this.tokens.push([name, Object.clone(m)]); @@ -194,7 +194,7 @@ Prototype.Legacy = Class.create({ var match = true, name, matches; for (var i = 0, token; token = this.tokens[i]; i++) { name = token[0], matches = token[1]; - if (!Prototype.Legacy.assertions[name](element, matches)) { + if (!Prototype.LegacySelector.assertions[name](element, matches)) { match = false; break; } } @@ -207,7 +207,7 @@ Prototype.Legacy = Class.create({ }, inspect: function() { - return "#"; + return "#"; } }); @@ -216,7 +216,7 @@ if (Prototype.BrowserFeatures.SelectorsAPI && // Versions of Safari 3 before 3.1.2 treat class names case-insensitively in // quirks mode. If we detect this behavior, we should use a different // approach. - Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + Prototype.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ var div = document.createElement('div'), span = document.createElement('span'); @@ -229,7 +229,7 @@ if (Prototype.BrowserFeatures.SelectorsAPI && })(); } -Object.extend(Prototype.Legacy, { +Object.extend(Prototype.LegacySelector, { _cache: { }, xpath: { @@ -251,13 +251,13 @@ Object.extend(Prototype.Legacy, { attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; - return new Template(Prototype.Legacy.xpath.operators[m[2]]).evaluate(m); + return new Template(Prototype.LegacySelector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { - var h = Prototype.Legacy.xpath.pseudos[m[1]]; + var h = Prototype.LegacySelector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); - return new Template(Prototype.Legacy.xpath.pseudos[m[1]]).evaluate(m); + return new Template(Prototype.LegacySelector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", @@ -277,8 +277,8 @@ Object.extend(Prototype.Legacy, { 'disabled': "[(@disabled) and (@type!='hidden')]", 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { - var e = m[6], p = Prototype.Legacy.patterns, - x = Prototype.Legacy.xpath, le, v, len = p.length, name; + var e = m[6], p = Prototype.LegacySelector.patterns, + x = Prototype.LegacySelector.xpath, le, v, len = p.length, name; var exclusion = []; while (e && le != e && (/\S/).test(e)) { @@ -296,25 +296,25 @@ Object.extend(Prototype.Legacy, { return "[not(" + exclusion.join(" and ") + ")]"; }, 'nth-child': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); }, 'nth-last-child': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); }, 'nth-of-type': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("position() ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("position() ", m); }, 'nth-last-of-type': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(last() + 1 - position()) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(last() + 1 - position()) ", m); }, 'first-of-type': function(m) { - m[6] = "1"; return Prototype.Legacy.xpath.pseudos['nth-of-type'](m); + m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-of-type'](m); }, 'last-of-type': function(m) { - m[6] = "1"; return Prototype.Legacy.xpath.pseudos['nth-last-of-type'](m); + m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-last-of-type'](m); }, 'only-of-type': function(m) { - var p = Prototype.Legacy.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + var p = Prototype.LegacySelector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); }, nth: function(fragment, m) { var mm, formula = m[6], predicate; @@ -371,7 +371,7 @@ Object.extend(Prototype.Legacy, { { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } ], - // for Prototype.Legacy.match and Element#match + // for Prototype.LegacySelector.match and Element#match assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); @@ -391,7 +391,7 @@ Object.extend(Prototype.Legacy, { attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Prototype.Legacy.operators[matches[2]](nodeValue, matches[5] || matches[6]); + return nodeValue && Prototype.LegacySelector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, @@ -466,19 +466,19 @@ Object.extend(Prototype.Legacy, { n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } - return Prototype.Legacy.handlers.unmark(results); + return Prototype.LegacySelector.handlers.unmark(results); }, // COMBINATOR FUNCTIONS descendant: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); @@ -495,7 +495,7 @@ Object.extend(Prototype.Legacy, { }, laterSibling: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; @@ -516,7 +516,7 @@ Object.extend(Prototype.Legacy, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { var uTagName = tagName.toUpperCase(); - var results = [], h = Prototype.Legacy.handlers; + var results = [], h = Prototype.LegacySelector.handlers; if (nodes) { if (combinator) { // fastlane for ordinary descendant combinators @@ -534,7 +534,7 @@ Object.extend(Prototype.Legacy, { }, id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Prototype.Legacy.handlers; + var targetNode = $(id), h = Prototype.LegacySelector.handlers; if (root == document) { // We don't have to deal with orphan nodes. Easy. @@ -562,7 +562,7 @@ Object.extend(Prototype.Legacy, { if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) - if (Prototype.Legacy.handlers.previousElementSibling(targetNode) == node) + if (Prototype.LegacySelector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } @@ -575,11 +575,11 @@ Object.extend(Prototype.Legacy, { className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); - return Prototype.Legacy.handlers.byClassName(nodes, root, className); + return Prototype.LegacySelector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { - if (!nodes) nodes = Prototype.Legacy.handlers.descendant([root]); + if (!nodes) nodes = Prototype.LegacySelector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; @@ -602,7 +602,7 @@ Object.extend(Prototype.Legacy, { attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Prototype.Legacy.operators[operator], results = []; + var handler = Prototype.LegacySelector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; @@ -614,52 +614,52 @@ Object.extend(Prototype.Legacy, { pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); - return Prototype.Legacy.pseudos[name](nodes, value, root); + return Prototype.LegacySelector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Prototype.Legacy.handlers.previousElementSibling(node)) continue; + if (Prototype.LegacySelector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Prototype.Legacy.handlers.nextElementSibling(node)) continue; + if (Prototype.LegacySelector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, false, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, true, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, "1", root, false, true); + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, "1", root, true, true); + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { - var p = Prototype.Legacy.pseudos; + var p = Prototype.LegacySelector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, @@ -677,7 +677,7 @@ Object.extend(Prototype.Legacy, { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; - var h = Prototype.Legacy.handlers, results = [], indexed = [], m; + var h = Prototype.LegacySelector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._countedByPrototype) { @@ -693,7 +693,7 @@ Object.extend(Prototype.Legacy, { if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; - var indices = Prototype.Legacy.pseudos.getIndices(a, b, nodes.length); + var indices = Prototype.LegacySelector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); @@ -714,8 +714,8 @@ Object.extend(Prototype.Legacy, { }, 'not': function(nodes, selector, root) { - var h = Prototype.Legacy.handlers, selectorType, m; - var exclusions = new Prototype.Legacy(selector).findElements(root); + var h = Prototype.LegacySelector.handlers, selectorType, m; + var exclusions = new Prototype.LegacySelector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); @@ -763,7 +763,7 @@ Object.extend(Prototype.Legacy, { }, matchElements: function(elements, expression) { - var matches = $$(expression), h = Prototype.Legacy.handlers; + var matches = $$(expression), h = Prototype.LegacySelector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._countedByPrototype) results.push(element); @@ -775,14 +775,14 @@ Object.extend(Prototype.Legacy, { if (Object.isNumber(expression)) { index = expression; expression = false; } - return Prototype.Legacy.matchElements(elements, expression || '*')[index || 0]; + return Prototype.LegacySelector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { - expressions = Prototype.Legacy.split(expressions.join(',')); - var results = [], h = Prototype.Legacy.handlers; + expressions = Prototype.LegacySelector.split(expressions.join(',')); + var results = [], h = Prototype.LegacySelector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Prototype.Legacy(expressions[i].strip()); + selector = new Prototype.LegacySelector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; @@ -790,7 +790,7 @@ Object.extend(Prototype.Legacy, { }); if (Prototype.Browser.IE) { - Object.extend(Prototype.Legacy.handlers, { + Object.extend(Prototype.LegacySelector.handlers, { // IE returns comment nodes on getElementsByTagName("*"). // Filter them out. concat: function(a, b) { diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy_selector/selector_engine.js similarity index 83% rename from vendor/legacy/selector_engine.js rename to vendor/legacy_selector/selector_engine.js index 21d5c6bca..cf6287508 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy_selector/selector_engine.js @@ -1,4 +1,4 @@ -//= require "repository/legacy" +//= require "repository/legacy_selector" Prototype.Selector = (function(engine) { function select(selector, scope) { @@ -15,4 +15,4 @@ Prototype.Selector = (function(engine) { match: match, filter: engine.matchElements }; -})(Prototype.Legacy); +})(Prototype.LegacySelector); From 678774cbd6ad70d9ea398a0cc18e31cb01caae30 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 26 Oct 2009 02:00:55 +0100 Subject: [PATCH 048/502] Minor changes to the Sizzle adapter. --- vendor/sizzle/selector_engine.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index 6980631c7..f460f3352 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -3,8 +3,9 @@ Prototype._original_property = window.Sizzle; Prototype.Selector = (function(engine) { function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } return elements; } From 749badbd4d118066f15660a617540ee468ae014a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 30 Oct 2009 04:19:17 -0500 Subject: [PATCH 049/502] Revised pass on `Element.Layout` and `Element.Offset` classes. Unit tests are incomplete. --- src/dom.js | 1 + src/dom/layout.js | 575 +++++++++++++++++++++++++++++++++ test/unit/fixtures/layout.html | 105 ++++++ test/unit/layout_test.js | 75 +++++ 4 files changed, 756 insertions(+) create mode 100644 src/dom/layout.js create mode 100644 test/unit/fixtures/layout.html create mode 100644 test/unit/layout_test.js diff --git a/src/dom.js b/src/dom.js index f9ff3ff30..31f7605f4 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,6 +21,7 @@ //= require "dom/dom" +//= require "dom/layout" //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/src/dom/layout.js b/src/dom/layout.js new file mode 100644 index 000000000..2547cd434 --- /dev/null +++ b/src/dom/layout.js @@ -0,0 +1,575 @@ + + +(function() { + + // Converts a CSS percentage value to a decimal. + // Ex: toDecimal("30%"); // -> 0.3 + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + // Can be called like this: + // getPixelValue("11px"); + // Or like this: + // getPixelValue(someElement, 'paddingTop'); + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; + } + + // Non-IE browsers will always return pixels. + if ((/^\d+(px)?$/i).test(value)) { + return window.parseInt(value, 10); + } + + // When IE gives us something other than a pixel value, this technique + // (invented by Dean Edwards) will convert it to pixels. + if (element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + // For other browsers, we have to do a bit of work. + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } + + // If we get this far, we should probably give up. + return null; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + /** + * class Element.Layout < Hash + * + * A set of key/value pairs representing measurements of various + * dimensions of an element. + **/ + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + // The 'preCompute' boolean tells us whether we should fetch all values + // at once. If so, we should do setup/teardown only once. We set a flag + // so that we can ignore calls to `_begin` and `_end` elsewhere. + if (preCompute) { + this._preComputing = true; + this._begin(); + } + Element.Layout.PROPERTIES.each( function(property) { + if (preCompute) { + this._compute(property); + } else { + this._set(property, null); + } + }, this); + if (preCompute) { + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { + throw "Cannot set a composite property."; + } + + return this._set(property, toCSSPixels(value)); + }, + + get: function($super, property) { + // Try to fetch from the cache. + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + // `_begin` and `_end` are two functions that are called internally + // before and after any measurement is done. In certain conditions (e.g., + // when hidden), elements need a "preparation" phase that ensures + // accuracy of measurements. + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + // Remember the original values for some styles we're going to alter. + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + // We store them so that the `_end` function can retrieve them later. + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + var layout = element.getLayout(); + element.setStyle({ + position: 'absolute', + visibility: 'visible', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + // If the element's width is the same both before and after + // we set absolute positioning, that means: + // (a) it was already absolutely-positioned; or + // (b) it has an explicitly-set width, instead of width: auto. + // Either way, it means the element is the width it needs to be + // in order to report an accurate height. + newWidth = window.parseInt(width, 10); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = window.parseInt(width, 10); + } else { + // If not, that means the element's width depends upon the width of + // its parent. + var parent = element.up(), pLayout = parent.getLayout(); + + newWidth = pLayout.get('width') - + layout.get('margin-left') - + layout.get('border-left') - + layout.get('padding-left') - + layout.get('padding-right') - + layout.get('border-right') - + layout.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + // The element is now ready for measuring. + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + + var value = COMPUTATIONS[property].call(this, this.element); + this._set(property, value); + return value; + } + }); + + Object.extend(Element.Layout, { + // All measurable properties. + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + // Sums of other properties. Can be read but not written. + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + return getPixelValue(element, 'top'); + }, + + 'bottom': function(element) { + return getPixelValue(element, 'bottom'); + }, + + 'left': function(element) { + return getPixelValue(element, 'left'); + }, + + 'right': function(element) { + return getPixelValue(element, 'right'); + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return element.clientTop || + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return element.clientBottom || + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return element.clientLeft || + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return element.clientRight || + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + /** + * class Element.Offset + * + * A representation of the top- and left-offsets of an element relative to + * another. + **/ + Element.Offset = Class.create({ + /** + * new Element.Offset(left, top) + * + * Instantiates an [[Element.Offset]]. You shouldn't need to call this + * directly. + **/ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + // Act like an array. + this[0] = this.left; + this[1] = this.top; + }, + + /** + * Element.Offset#relativeTo(offset) -> Element.Offset + * - offset (Element.Offset): Another offset to compare to. + * + * Returns a new [[Element.Offset]] with its origin at the given + * `offset`. Useful for determining an element's distance from another + * arbitrary element. + **/ + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + /** + * Element.Offset#inspect() -> String + **/ + inspect: function() { + return "# Array + **/ + toArray: function() { + return [this.left, this.top]; + } + }); + + /** + * Element.getLayout(@element) -> Element.Layout + * + * Returns an instance of [[Element.Layout]] for measuring an element's + * dimensions. + * + * Note that this method returns a _new_ `Element.Layout` object each time + * it's called. If you want to take advantage of measurement caching, + * retain a reference to one `Element.Layout` object, rather than calling + * `Element.getLayout` whenever you need a measurement. You should call + * `Element.getLayout` again only when the values in an existing + * `Element.Layout` object have become outdated. + **/ + function getLayout(element) { + return new Element.Layout(element); + } + + /** + * Element.measure(@element, property) -> Number + * + * Gives the pixel value of `element`'s dimension specified by + * `property`. + * + * Useful for one-off measurements of elements. If you find yourself + * calling this method frequently over short spans of code, you might want + * to call [[Element.getLayout]] and operate on the [[Element.Layout]] + * object itself (thereby taking advantage of measurement caching). + **/ + function measure(element, property) { + return $(element).getLayout().get(property); + } + + /** + * Element.cumulativeOffset(@element) -> Element.Offset + * + * Returns the offsets of `element` from the top left corner of the + * document. + **/ + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.positionedOffset(@element) -> Element.Offset + * + * Returns `element`'s offset relative to its closest positioned ancestor + * (the element that would be returned by [[Element.getOffsetParent]]). + **/ + function positionedOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.cumulativeScrollOffset(@element) -> Element.Offset + * + * Calculates the cumulative scroll offset of an element in nested + * scrolling containers. + **/ + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.viewportOffset(@element) -> Array + * + * Returns the X/Y coordinates of element relative to the viewport. + **/ + function viewportOffset(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + var tagName = element.tagName, O = Prototype.Browser.Opera; + do { + if (!O || tagName && tagName.toUpperCase() === 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset + }); + + // If the browser supports the nonstandard `getBoundingClientRect` + // (currently only IE and Firefox), it becomes far easier to obtain + // true offsets. + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + var rect = element.getBoundingClientRect(); + return new Element.Offset(rect.left, rect.top); + }, + + cumulativeOffset: function(element) { + element = $(element); + var docOffset = $(document.documentElement).viewportOffset(), + elementOffset = element.viewportOffset(); + return elementOffset.relativeTo(docOffset); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + var isBody = (parent.nodeName.toUpperCase() === 'BODY'); + var eOffset = element.viewportOffset(), + pOffset = isBody ? viewportOffset(parent) : parent.viewportOffset(); + return eOffset.relativeTo(pOffset); + } + }); + } +})(); \ No newline at end of file diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html new file mode 100644 index 000000000..918e20c6c --- /dev/null +++ b/test/unit/fixtures/layout.html @@ -0,0 +1,105 @@ +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    +
    + + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    +
    + + + +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + \ No newline at end of file diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js new file mode 100644 index 000000000..3cbb0518c --- /dev/null +++ b/test/unit/layout_test.js @@ -0,0 +1,75 @@ +function isDisplayed(element) { + var originalElement = element; + + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; +} + +new Test.Unit.Runner({ + setup: function() { + }, + + 'test layout on absolutely-positioned elements': function() { + var layout = $('box1').getLayout(); + + this.assertEqual(242, layout.get('width'), 'width' ); + this.assertEqual(555, layout.get('height'), 'height'); + + this.assertEqual(3, layout.get('border-left'), 'border-left'); + this.assertEqual(10, layout.get('padding-top'), 'padding-top'); + this.assertEqual(1020, layout.get('top'), 'top'); + + this.assertEqual(25, layout.get('left'), 'left'); + }, + + 'test layout on elements with display: none and exact width': function() { + var layout = $('box2').getLayout(); + + this.assertEqual(500, layout.get('width'), 'width'); + this.assertEqual(3, layout.get('border-right'), 'border-right'); + this.assertEqual(10, layout.get('padding-bottom'), 'padding-bottom'); + }, + + 'test layout on elements with display: none and width: auto': function() { + var layout = $('box3').getLayout(); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + + + this.assertEqual(364, layout.get('width'), 'width'); + this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); + this.assertEqual(3, layout.get('border-right'), 'border-top'); + this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); + }, + + 'test layout on elements with display: none ancestors': function() { + var layout = $('box4').getLayout(); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box4')), 'box should still be hidden'); + + // Width and height values are nonsensical for deeply-hidden elements. + this.assertEqual(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); + this.assertEqual(0, layout.get('margin-box-height'), 'height of a deeply-hidden element should be 0'); + + // But we can still get meaningful values for other measurements. + this.assertEqual(0, layout.get('border-right'), 'border-top'); + this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); + }, + + 'test positioning on absolutely-positioned elements': function() { + var layout = $('box5').getLayout(); + + this.assertEqual(30, layout.get('top'), 'top'); + this.assertEqual(60, layout.get('right'), 'right (percentage value)'); + + this.assertNull(layout.get('left'), 'left (should be null because none was set)'); + } +}); \ No newline at end of file From 7aa195b650d6c415a6c90753b19f409e3ec15c3a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 20:03:17 -0600 Subject: [PATCH 050/502] Update layout code. Roughly compatible with IE 6-8... so far. --- src/dom/layout.js | 32 ++++++++++++++++---------------- test/unit/layout_test.js | 13 +++++++++---- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 2547cd434..204fcbca8 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -30,7 +30,7 @@ // When IE gives us something other than a pixel value, this technique // (invented by Dean Edwards) will convert it to pixels. - if (element.runtimeStyle) { + if (/\d/.test(value) && element.runtimeStyle) { var style = element.style.left, rStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = value || 0; @@ -38,7 +38,7 @@ element.style.left = style; element.runtimeStyle.left = rStyle; - return value; + return value; } // For other browsers, we have to do a bit of work. @@ -57,7 +57,7 @@ } // If we get this far, we should probably give up. - return null; + return 0; } function toCSSPixels(number) { @@ -154,10 +154,9 @@ var position = element.getStyle('position'), width = element.getStyle('width'); - var layout = element.getLayout(); element.setStyle({ position: 'absolute', - visibility: 'visible', + visibility: 'hidden', display: 'block' }); @@ -177,15 +176,16 @@ } else { // If not, that means the element's width depends upon the width of // its parent. - var parent = element.up(), pLayout = parent.getLayout(); + var parent = element.parentNode, pLayout = $(parent).getLayout(); + newWidth = pLayout.get('width') - - layout.get('margin-left') - - layout.get('border-left') - - layout.get('padding-left') - - layout.get('padding-right') - - layout.get('border-right') - - layout.get('margin-right'); + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); } element.setStyle({ width: newWidth + 'px' }); @@ -333,22 +333,22 @@ }, 'border-top': function(element) { - return element.clientTop || + return Object.isNumber(element.clientTop) ? element.clientTop : getPixelValue(element, 'borderTopWidth'); }, 'border-bottom': function(element) { - return element.clientBottom || + return Object.isNumber(element.clientBottom) ? element.clientBottom : getPixelValue(element, 'borderBottomWidth'); }, 'border-left': function(element) { - return element.clientLeft || + return Object.isNumber(element.clientLeft) ? element.clientLeft : getPixelValue(element, 'borderLeftWidth'); }, 'border-right': function(element) { - return element.clientRight || + return Object.isNumber(element.clientRight) ? element.clientRight : getPixelValue(element, 'borderRightWidth'); }, diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 3cbb0518c..5b057925a 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -31,29 +31,34 @@ new Test.Unit.Runner({ 'test layout on elements with display: none and exact width': function() { var layout = $('box2').getLayout(); + this.assert(!isDisplayed($('box3')), 'box should be hidden'); + this.assertEqual(500, layout.get('width'), 'width'); this.assertEqual(3, layout.get('border-right'), 'border-right'); this.assertEqual(10, layout.get('padding-bottom'), 'padding-bottom'); + + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test layout on elements with display: none and width: auto': function() { var layout = $('box3').getLayout(); - // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + this.assert(!isDisplayed($('box3')), 'box should be hidden'); - this.assertEqual(364, layout.get('width'), 'width'); this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); this.assertEqual(3, layout.get('border-right'), 'border-top'); this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test layout on elements with display: none ancestors': function() { var layout = $('box4').getLayout(); // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box4')), 'box should still be hidden'); + this.assert(!isDisplayed($('box4')), 'box should be hidden'); // Width and height values are nonsensical for deeply-hidden elements. this.assertEqual(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); From 97ea37d3d55a1d45793bda208ef918e4f06199fc Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:52:07 -0600 Subject: [PATCH 051/502] A bunch of fixes for offsets. --- src/dom/layout.js | 57 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 204fcbca8..854f56622 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -79,6 +79,18 @@ return true; } + var hasLayout = Prototype.K; + + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + /** * class Element.Layout < Hash * @@ -411,7 +423,14 @@ * Element.Offset#inspect() -> String **/ inspect: function() { - return "#".interpolate(this); + }, + + /** + * Element.Offset#toString() -> String + **/ + toString: function() { + return "[#{left}, #{top}]".interpolate(this); }, /** @@ -477,17 +496,24 @@ * (the element that would be returned by [[Element.getOffsetParent]]). **/ function positionedOffset(element) { + // Account for the margin of the element. + var layout = element.getLayout(); + var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; + if (isBody(element)) break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + return new Element.Offset(valueL, valueT); } @@ -544,6 +570,10 @@ viewportOffset: viewportOffset }); + function isBody(element) { + return $w('BODY HTML').include(element.nodeName.toUpperCase()); + } + // If the browser supports the nonstandard `getBoundingClientRect` // (currently only IE and Firefox), it becomes far easier to obtain // true offsets. @@ -565,11 +595,26 @@ positionedOffset: function(element) { element = $(element); var parent = element.getOffsetParent(); - var isBody = (parent.nodeName.toUpperCase() === 'BODY'); + + // When the BODY is the offsetParent, IE6 mistakenly reports the + // parent as HTML. Use that as the litmus test to fix another + // annoying IE6 quirk. + if (parent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + var eOffset = element.viewportOffset(), - pOffset = isBody ? viewportOffset(parent) : parent.viewportOffset(); - return eOffset.relativeTo(pOffset); + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + // Account for the margin of the element. + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); } - }); + }); } })(); \ No newline at end of file From ac451d6d8fe9dfaa20066984e7c39c1dbcaa4c35 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:52:19 -0600 Subject: [PATCH 052/502] Tweaks to unit tests. --- test/unit/layout_test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 5b057925a..eec7187b3 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -57,7 +57,6 @@ new Test.Unit.Runner({ 'test layout on elements with display: none ancestors': function() { var layout = $('box4').getLayout(); - // Ensure that we cleaned up after ourselves. this.assert(!isDisplayed($('box4')), 'box should be hidden'); // Width and height values are nonsensical for deeply-hidden elements. @@ -67,6 +66,9 @@ new Test.Unit.Runner({ // But we can still get meaningful values for other measurements. this.assertEqual(0, layout.get('border-right'), 'border-top'); this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test positioning on absolutely-positioned elements': function() { @@ -75,6 +77,6 @@ new Test.Unit.Runner({ this.assertEqual(30, layout.get('top'), 'top'); this.assertEqual(60, layout.get('right'), 'right (percentage value)'); - this.assertNull(layout.get('left'), 'left (should be null because none was set)'); + this.assertEqual(340, layout.get('left'), 'left'); } }); \ No newline at end of file From 093c0cce4bbb89b88b0af20356faa83906b3aafa Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:54:25 -0600 Subject: [PATCH 053/502] Optimize retrieving of top|left|right|bottom properties. Add some documentation. Disable setting of properties for now. --- src/dom/layout.js | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 10 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 854f56622..3152ed6b9 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -96,8 +96,115 @@ * * A set of key/value pairs representing measurements of various * dimensions of an element. + * + *

    Overview

    + * + * The `Element.Layout` class is a specialized way to measure elements. + * It helps mitigate: + * + * * The convoluted steps often needed to get common measurements for + * elements. + * * The tendency of browsers to report measurements in non-pixel units. + * * The quirks that lead some browsers to report inaccurate measurements. + * * The difficulty of measuring elements that are hidden. + * + *

    Usage

    + * + * Instantiate an `Element.Layout` class by passing an element into the + * constructor: + * + * var layout = new Element.Layout(someElement); + * + * You can also use [[Element.getLayout]], if you prefer. + * + * Once you have a layout object, retrieve properties using [[Hash]]'s + * familiar `get` and `set` syntax. + * + * layout.get('width'); //-> 400 + * layout.get('top'); //-> 180 + * + * The following are the CSS-related properties that can be retrieved. + * Nearly all of them map directly to their property names in CSS. (The + * only exception is for borders — e.g., `border-width` instead of + * `border-left-width`.) + * + * * `height` + * * `width` + * * `top` + * * `left` + * * `right` + * * `bottom` + * * `border-left` + * * `border-right` + * * `border-top` + * * `border-bottom` + * * `padding-left` + * * `padding-right` + * * `padding-top` + * * `padding-bottom` + * * `margin-top` + * * `margin-bottom` + * * `margin-left` + * * `margin-right` + * + * In addition, these "composite" properties can be retrieved: + * + * * `padding-box-width` (width of the content area, from the beginning of + * the left padding to the end of the right padding) + * * `padding-box-height` (height of the content area, from the beginning + * of the top padding to the end of the bottom padding) + * * `border-box-width` (width of the content area, from the outer edge of + * the left border to the outer edge of the right border) + * * `border-box-height` (height of the content area, from the outer edge + * of the top border to the outer edge of the bottom border) + * * `margin-box-width` (width of the content area, from the beginning of + * the left margin to the end of the right margin) + * * `margin-box-height` (height of the content area, from the beginning + * of the top margin to the end of the bottom margin) + * + *

    Caching

    + * + * Because these properties can be costly to retrieve, `Element.Layout` + * behaves differently from an ordinary [[Hash]]. + * + * First: by default, values are "lazy-loaded" — they aren't computed + * until they're retrieved. To measure all properties at once, pass + * a second argument into the constructor: + * + * var layout = new Element.Layout(someElement, true); + * + * Second: once a particular value is computed, it's cached. Asking for + * the same property again will return the original value without + * re-computation. This means that **an instance of `Element.Layout` + * becomes stale when the element's dimensions change**. When this + * happens, obtain a new instance. + * + *

    Hidden elements

    + * + * Because it's a common case to want the dimensions of a hidden element + * (e.g., for animations), it's possible to measure elements that are + * hidden with `display: none`. + * + * However, **it's only possible to measure a hidden element if its parent + * is visible**. If its parent (or any other ancestor) is hidden, any + * width and height measurements will return `0`, as will measurements for + * `top|bottom|left|right`. + * **/ Element.Layout = Class.create(Hash, { + /** + * new Element.Layout(element[, preCompute]) + * - element (Element): The element to be measured. + * - preCompute (Boolean): Whether to compute all values at once. + * + * Declare a new layout hash. + * + * The `preCompute` argument determines whether measurements will be + * lazy-loaded or not. If you plan to use many different measurements, + * it's often more performant to pre-compute, as it minimizes the + * amount of overhead needed to measure. If you need only one or two + * measurements, it's probably not worth it. + **/ initialize: function($super, element, preCompute) { $super(); this.element = $(element); @@ -125,14 +232,25 @@ return Hash.prototype.set.call(this, property, value); }, + + // TODO: Investigate. set: function(property, value) { - if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { - throw "Cannot set a composite property."; - } - - return this._set(property, toCSSPixels(value)); + throw "Properties of Element.Layout are read-only."; + // if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { + // throw "Cannot set a composite property."; + // } + // + // return this._set(property, toCSSPixels(value)); }, + /** + * Element.Layout#get(property) -> Number + * - property (String): One of the properties defined in + * [[Element.Layout.PROPERTIES]]. + * + * Retrieve the measurement specified by `property`. Will throw an error + * if the property is invalid. + **/ get: function($super, property) { // Try to fetch from the cache. var value = $super(property); @@ -228,9 +346,20 @@ Object.extend(Element.Layout, { // All measurable properties. + /** + * Element.Layout.PROPERTIES = Array + * + * A list of all measurable properties. + **/ PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), - // Sums of other properties. Can be read but not written. + /** + * Element.Layout.COMPOSITE_PROPERTIES = Array + * + * A list of all composite properties. Composite properties don't map + * directly to CSS properties — they're combinations of other + * properties. + **/ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), COMPUTATIONS: { @@ -313,19 +442,37 @@ }, 'top': function(element) { - return getPixelValue(element, 'top'); + var offset = element.positionedOffset(); + return offset.top; }, 'bottom': function(element) { - return getPixelValue(element, 'bottom'); + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + // + // return getPixelValue(element, 'bottom'); }, 'left': function(element) { - return getPixelValue(element, 'left'); + var offset = element.positionedOffset(); + return offset.left; }, 'right': function(element) { - return getPixelValue(element, 'right'); + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + // + // return getPixelValue(element, 'right'); }, 'padding-top': function(element) { @@ -382,6 +529,27 @@ } }); + // An easier way to compute right and bottom offsets. + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + /** * class Element.Offset * From 62d0430f3b98153af49b01f7972e75cf2264ce13 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 10:56:01 -0600 Subject: [PATCH 054/502] Fix Element.viewportOffset on nested elements in Opera < 9.5 and remove browser sniff. (cherry picked from commit 0d2d18fb7297fb945ca6983b843c5bcf367c721c) --- src/dom/layout.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 3152ed6b9..d0d4dca13 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -707,21 +707,23 @@ * Returns the X/Y coordinates of element relative to the viewport. **/ function viewportOffset(forElement) { - var valueT = 0, valueL = 0; + var valueT = 0, valueL = 0, docBody = document.body; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix - if (element.offsetParent == document.body && + if (element.offsetParent == docBody && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); - element = forElement; - var tagName = element.tagName, O = Prototype.Browser.Opera; + element = forElement; do { - if (!O || tagName && tagName.toUpperCase() === 'BODY') { + // Opera < 9.5 sets scrollTop/Left on both HTML and BODY elements. + // Other browsers set it only on the HTML element. The BODY element + // can be skipped since its scrollTop/Left should always be 0. + if (element != docBody) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } From c89d6eac95f75f778ffea3411d1d27e99ede4976 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 10:58:45 -0600 Subject: [PATCH 055/502] Fix incorrect offset in Element.viewportOffset on IE < 8. (cherry picked from commit 3afb0002cbd31726187338c5119657b76111f80c) --- src/dom/layout.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index d0d4dca13..444da63d0 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -751,8 +751,13 @@ Element.addMethods({ viewportOffset: function(element) { element = $(element); - var rect = element.getBoundingClientRect(); - return new Element.Offset(rect.left, rect.top); + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + // The HTML element on IE < 8 has a 2px border by default, giving + // an incorrect offset. We correct this by subtracting clientTop + // and clientLeft. + return new Element.Offset(rect.top - docEl.clientTop, + rect.left - docEl.clientLeft); }, cumulativeOffset: function(element) { From 720ee56ae513cd3404a0d7c3eacd130259f426b1 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 14:47:38 -0600 Subject: [PATCH 056/502] Fix bug introduced in rewrite of Nick's patch. I'm an idiot. --- src/dom/layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 444da63d0..7f235f455 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -756,8 +756,8 @@ // The HTML element on IE < 8 has a 2px border by default, giving // an incorrect offset. We correct this by subtracting clientTop // and clientLeft. - return new Element.Offset(rect.top - docEl.clientTop, - rect.left - docEl.clientLeft); + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); }, cumulativeOffset: function(element) { From 01a229011a9318cb1c7b39db73ac8ee0d946ac5a Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 11 Nov 2009 17:32:19 -0500 Subject: [PATCH 057/502] Avoid repeating declaration statements. --- CHANGELOG | 2 ++ src/dom/dom.js | 72 ++++++++++++++++++++++---------------------- src/dom/selector.js | 16 +++++----- src/lang/class.js | 4 +-- src/lang/string.js | 9 +++--- src/lang/template.js | 5 +-- src/prototype.js | 6 ++-- 7 files changed, 60 insertions(+), 54 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2d4e5953c..10548fe77 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Avoid repeating declaration statements where it makes sense, for slightly better runtime performance and minification. (kangax) + * Make `Event.stopObserving` return element in all cases. [#810 state:resolved] (Yaffle, Tobie Langel) * String#startsWith, String#endsWith performance optimization (Yaffle, Tobie Langel, kangax) diff --git a/src/dom/dom.js b/src/dom/dom.js index 0f0e247c3..ae9bc21f1 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -115,9 +115,9 @@ if (!Node.ELEMENT_NODE) { // setAttribute is broken in IE (particularly when setting name attribute) // see: http://msdn.microsoft.com/en-us/library/ms536389.aspx var SETATTRIBUTE_IGNORES_NAME = (function(){ - var elForm = document.createElement("form"); - var elInput = document.createElement("input"); - var root = document.documentElement; + var elForm = document.createElement("form"), + elInput = document.createElement("input"), + root = document.documentElement; elInput.setAttribute("name", "test"); elForm.appendChild(elInput); root.appendChild(elForm); @@ -443,8 +443,9 @@ Element.Methods = { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; @@ -1055,16 +1056,16 @@ Element.Methods = { // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; + var els = element.style, + originalVisibility = els.visibility, + originalPosition = els.position, + originalDisplay = els.display; els.visibility = 'hidden'; if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari els.position = 'absolute'; els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; + var originalWidth = element.clientWidth, + originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; @@ -1209,11 +1210,11 @@ Element.Methods = { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = Element.positionedOffset(element); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); @@ -1241,8 +1242,8 @@ Element.Methods = { if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; @@ -1310,9 +1311,10 @@ Element.Methods = { * as properties: `{ left: leftValue, top: topValue }`. **/ viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; + var valueT = 0, + valueL = 0, + element = forElement; + do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; @@ -1403,12 +1405,11 @@ Element.Methods = { // find page position of source source = $(source); - var p = Element.viewportOffset(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; // find coordinate system to use element = $(element); - var delta = [0, 0]; - var parent = null; + // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(element, 'position') == 'absolute') { @@ -1618,10 +1619,9 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations = (function(){ - var classProp = 'className'; - var forProp = 'for'; - - var el = document.createElement('div'); + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); // try "className" first (IE <8) el.setAttribute(classProp, 'x'); @@ -1666,10 +1666,9 @@ else if (Prototype.Browser.IE) { }, _getEv: (function(){ - var el = document.createElement('div'); + var el = document.createElement('div'), f; el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); - var f; // IE<8 if (String(value).indexOf('{') > -1) { @@ -1846,8 +1845,8 @@ if ('outerHTML' in document.documentElement) { var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); @@ -1962,8 +1961,8 @@ Element.extend = (function() { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { - var id = '_' + (Math.random()+'').slice(2); - var el = document.createElement(tagName); + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; @@ -2228,8 +2227,9 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - var element = document.createElement(tagName); - var proto = element['__proto__'] || element.constructor.prototype; + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + element = null; return proto; } diff --git a/src/dom/selector.js b/src/dom/selector.js index 7692a62b2..7ba29428d 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -349,8 +349,8 @@ Object.extend(Selector, { return '[' + fragment + "= " + mm[1] + ']'; if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; + var a = mm[1] ? Number(mm[1]) : 1, + b = mm[2] ? Number(mm[2]) : 0; predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + "((#{fragment} - #{b}) div #{a} >= 0)]"; return new Template(predicate).evaluate({ @@ -716,9 +716,11 @@ Object.extend(Selector, { if (node.nodeIndex == formula) results.push(node); } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); + + var a = m[1] ? Number(m[1]) : 1, + b = m[2] ? Number(m[2]) : 0, + indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); @@ -739,8 +741,8 @@ Object.extend(Selector, { }, 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); + var h = Selector.handlers, selectorType, m, + exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); diff --git a/src/lang/class.js b/src/lang/class.js index 68b1fe649..1ac0750ef 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -135,8 +135,8 @@ var Class = (function() { * //-> alerts "You should probably run. He looks really mad." **/ function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); // IE6 doesn't enumerate toString and valueOf properties, // Force copy if they're not coming from Object.prototype. diff --git a/src/lang/string.js b/src/lang/string.js index 26c3cf22e..28b3f229f 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -167,8 +167,8 @@ Object.extend(String.prototype, (function() { * returns them as an array of strings. **/ function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); @@ -251,8 +251,9 @@ Object.extend(String.prototype, (function() { return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); if (key in hash) { diff --git a/src/lang/template.js b/src/lang/template.js index cb9020d1d..8b396a89d 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -133,8 +133,9 @@ var Template = Class.create({ var before = match[1] || ''; if (before == '\\') return match[2]; - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); if (match == null) return before; diff --git a/src/prototype.js b/src/prototype.js index 21089a40e..5f78f7824 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -35,9 +35,9 @@ var Prototype = { if (typeof window.HTMLDivElement !== 'undefined') return true; - var div = document.createElement('div'); - var form = document.createElement('form'); - var isSupported = false; + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; From 1f167f8754ace92c3a33bf7a275a46d42e71746d Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 11 Nov 2009 17:43:25 -0500 Subject: [PATCH 058/502] `if` should be followed by space. --- src/ajax/response.js | 4 ++-- src/dom/dom.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ajax/response.js b/src/ajax/response.js index 4fd9baa2e..658df87e1 100644 --- a/src/ajax/response.js +++ b/src/ajax/response.js @@ -64,14 +64,14 @@ Ajax.Response = Class.create({ var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } - if(readyState == 4) { + if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); diff --git a/src/dom/dom.js b/src/dom/dom.js index ae9bc21f1..a3db44c5d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1802,7 +1802,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { + if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); From f40fd5a7d60663f0d1f593a913d966382280d716 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Thu, 12 Nov 2009 21:15:52 -0500 Subject: [PATCH 059/502] Remove redundant ternary. --- CHANGELOG | 2 ++ src/lang/string.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 10548fe77..f762443fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Remove redundant ternary. (kangax) + * Avoid repeating declaration statements where it makes sense, for slightly better runtime performance and minification. (kangax) * Make `Event.stopObserving` return element in all cases. [#810 state:resolved] (Yaffle, Tobie Langel) diff --git a/src/lang/string.js b/src/lang/string.js index 28b3f229f..e43aa79ce 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -483,7 +483,7 @@ Object.extend(String.prototype, (function() { truncate: truncate, // Firefox 3.5+ supports String.prototype.trim // (`trim` is ~ 5x faster than `strip` in FF3.5) - strip: String.prototype.trim ? String.prototype.trim : strip, + strip: String.prototype.trim || strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, From c272e400422af372d2f8f5039ec8a62369171a18 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Thu, 12 Nov 2009 21:17:37 -0500 Subject: [PATCH 060/502] Minor optimization in class module. --- src/lang/class.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/class.js b/src/lang/class.js index 1ac0750ef..3f59bb6d7 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -59,7 +59,7 @@ var Class = (function() { parent.subclasses.push(klass); } - for (var i = 0; i < properties.length; i++) + for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) @@ -150,7 +150,7 @@ var Class = (function() { for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { + value.argumentNames()[0] == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; From 829800834def0ac16a09c22df95f371c84ba9cfd Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Thu, 12 Nov 2009 21:21:42 -0500 Subject: [PATCH 061/502] Do not create translations object every time method is called. --- src/dom/event.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 0d86e1108..b57d3c4d6 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -379,12 +379,12 @@ window.addEventListener('unload', Prototype.emptyFunction, false); - var _getDOMEventName = Prototype.K; + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { - var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - return eventName in translations ? translations[eventName] : eventName; + return (translations[eventName] || eventName); }; } From 9ef3f8d2ed6a2ba6e3b5eab8e50983a1abb1c957 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Fri, 13 Nov 2009 12:27:10 -0500 Subject: [PATCH 062/502] Eliminate runtime forking and long method lookup in `Element.hasAttribute`. --- CHANGELOG | 2 ++ src/dom/dom.js | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f762443fd..a19404987 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Eliminate runtime forking and long method lookup in `Element.hasAttribute`. (kangax) + * Remove redundant ternary. (kangax) * Avoid repeating declaration statements where it makes sense, for slightly better runtime performance and minification. (kangax) diff --git a/src/dom/dom.js b/src/dom/dom.js index a3db44c5d..7876327c3 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2036,10 +2036,14 @@ Element.extend = (function() { return extend; })(); -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} /** * Element.addMethods(methods) -> undefined From 82c9b9c78336dce758cd6cff4cc17216fd1c9248 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Fri, 13 Nov 2009 14:21:14 -0500 Subject: [PATCH 063/502] Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. --- CHANGELOG | 2 ++ src/dom/dom.js | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a19404987..f1d99bfae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. (kangax) + * Eliminate runtime forking and long method lookup in `Element.hasAttribute`. (kangax) * Remove redundant ternary. (kangax) diff --git a/src/dom/dom.js b/src/dom/dom.js index 7876327c3..6489c4f52 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1868,11 +1868,17 @@ Element._returnOffset = function(l, t) { }; Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } return $A(div.childNodes); }; From 18f2ac65c3c168099b6b16a19c671dda63c3fa6f Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Fri, 13 Nov 2009 14:28:27 -0500 Subject: [PATCH 064/502] Optimize Element#immediateDescendants. --- CHANGELOG | 2 ++ src/dom/dom.js | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f1d99bfae..31a86cb73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Optimize Element#immediateDescendants. (kangax, Tobie Langel) + * Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. (kangax) * Eliminate runtime forking and long method lookup in `Element.hasAttribute`. (kangax) diff --git a/src/dom/dom.js b/src/dom/dom.js index 6489c4f52..9ba46594a 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -536,10 +536,14 @@ Element.Methods = { * **This method is deprecated, please see [[Element.childElements]]**. **/ immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; }, /** From b3fc07922c5aa7a901faa72fad3ac001e1654ba5 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Fri, 13 Nov 2009 15:49:22 -0500 Subject: [PATCH 065/502] Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. --- CHANGELOG | 2 ++ src/lang/class.js | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 31a86cb73..326ba74fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. Replace with feature test and a simple boolean check at runtime. (kangax) + * Optimize Element#immediateDescendants. (kangax, Tobie Langel) * Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. (kangax) diff --git a/src/lang/class.js b/src/lang/class.js index 3f59bb6d7..b277e2bbc 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -9,6 +9,17 @@ * inheritance](http://prototypejs.org/learn/class-inheritance). **/ var Class = (function() { + + // Some versions of JScript fail to enumerate over properties, names of which + // correspond to non-enumerable properties in the prototype chain + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + // check actual property name, so that it works with augmented Object.prototype + if (p === 'toString') return false; + } + return true; + })(); + /** * Class.create([superclass][, methods...]) -> Class * - superclass (Class): The optional superclass to inherit methods from. @@ -138,9 +149,10 @@ var Class = (function() { var ancestor = this.superclass && this.superclass.prototype, properties = Object.keys(source); - // IE6 doesn't enumerate toString and valueOf properties, - // Force copy if they're not coming from Object.prototype. - if (!Object.keys({ toString: true }).length) { + // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties, + // Force copy if they're not Object.prototype ones. + // Do not copy other Object.prototype.* for performance reasons + if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) From cdb41a170fca3c491db68333b95f770ca5b11919 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 30 Nov 2009 11:12:26 +0100 Subject: [PATCH 066/502] Add sizzle to ext/. --- Rakefile | 9 +- ext/sizzle/repository/sizzle.js | 1015 +++++++++++++++++++++++++++++++ ext/sizzle/selector_engine.js | 34 ++ 3 files changed, 1054 insertions(+), 4 deletions(-) create mode 100644 ext/sizzle/repository/sizzle.js create mode 100644 ext/sizzle/selector_engine.js diff --git a/Rakefile b/Rakefile index ad8bea887..1bb7c10bf 100755 --- a/Rakefile +++ b/Rakefile @@ -43,7 +43,7 @@ module PrototypeHelper }.merge(options) require_sprockets - load_path = [SRC_DIR] + load_path = [SRC_DIR, File.join(ROOT_DIR, 'ext', 'sizzle')] if selector = options[:selector_engine] get_selector_engine(selector) @@ -98,10 +98,11 @@ module PrototypeHelper end def self.get_selector_engine(name) - file = File.join(ROOT_DIR, 'vendor', name, 'repository') - unless File.exists?(file) + submodule = File.join(ROOT_DIR, 'vendor', name, 'repository') + ext = File.join(ROOT_DIR, 'ext', name) + unless File.exist?(submodule) || File.exist?(ext) get_submodule('the required selector engine', "#{name}/repository") - unless File.exists?(file) + unless File.exist?(submodule) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" exit end diff --git a/ext/sizzle/repository/sizzle.js b/ext/sizzle/repository/sizzle.js new file mode 100644 index 000000000..801b73196 --- /dev/null +++ b/ext/sizzle/repository/sizzle.js @@ -0,0 +1,1015 @@ +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + // Opera can't find a second classname (in 9.6) + if ( div.getElementsByClassName("e").length === 0 ) + return; + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +window.Sizzle = Sizzle; + +})(); diff --git a/ext/sizzle/selector_engine.js b/ext/sizzle/selector_engine.js new file mode 100644 index 000000000..f460f3352 --- /dev/null +++ b/ext/sizzle/selector_engine.js @@ -0,0 +1,34 @@ +Prototype._original_property = window.Sizzle; +//= require "repository/sizzle" + +Prototype.Selector = (function(engine) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + function select(selector, scope) { + return extend(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + function filter(elements, selector) { + return extend(engine.matches(selector, elements)); + } + + return { + engine: engine, + select: select, + match: match, + filter: filter + }; +})(Sizzle); + +// Restore globals. +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; From 74c5d45511b9c99d21f3652d6134a5deb1ddfe6c Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Mon, 30 Nov 2009 12:39:44 +0100 Subject: [PATCH 067/502] Fix custom selector engine load path resolution. --- Rakefile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Rakefile b/Rakefile index 1bb7c10bf..da242d524 100755 --- a/Rakefile +++ b/Rakefile @@ -43,11 +43,10 @@ module PrototypeHelper }.merge(options) require_sprockets - load_path = [SRC_DIR, File.join(ROOT_DIR, 'ext', 'sizzle')] + load_path = [SRC_DIR] if selector = options[:selector_engine] - get_selector_engine(selector) - load_path << File.join(ROOT_DIR, 'vendor', selector) + load_path << get_selector_engine(selector) end secretary = Sprockets::Secretary.new( @@ -98,14 +97,15 @@ module PrototypeHelper end def self.get_selector_engine(name) - submodule = File.join(ROOT_DIR, 'vendor', name, 'repository') - ext = File.join(ROOT_DIR, 'ext', name) - unless File.exist?(submodule) || File.exist?(ext) - get_submodule('the required selector engine', "#{name}/repository") - unless File.exist?(submodule) - puts "The selector engine you required isn't available at vendor/#{name}.\n\n" - exit - end + submodule_path = File.join(ROOT_DIR, "vendor", name) + return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) + ext_path = File.join(ROOT_DIR, "ext", name) + return ext_path if File.exist?(ext_path) + + get_submodule('the required selector engine', "#{name}/repository") + unless File.exist?(submodule) + puts "The selector engine you required isn't available at vendor/#{name}.\n\n" + exit end end From 9d4711281d415edce26abdcead012bedebb674c7 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 2 Dec 2009 00:47:16 +0100 Subject: [PATCH 068/502] Fix typo in Rakefile. --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index da242d524..6d569862e 100755 --- a/Rakefile +++ b/Rakefile @@ -103,7 +103,7 @@ module PrototypeHelper return ext_path if File.exist?(ext_path) get_submodule('the required selector engine', "#{name}/repository") - unless File.exist?(submodule) + unless File.exist?(submodule_path) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" exit end From 797c231bfa5c8400df763f7568955714f0ddf05c Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 2 Dec 2009 14:18:01 -0600 Subject: [PATCH 069/502] Move ext/sizzle into src so it's available in the default Sprockets load path. --- {ext/sizzle => src}/selector_engine.js | 2 +- {ext/sizzle/repository => src}/sizzle.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {ext/sizzle => src}/selector_engine.js (96%) rename {ext/sizzle/repository => src}/sizzle.js (100%) diff --git a/ext/sizzle/selector_engine.js b/src/selector_engine.js similarity index 96% rename from ext/sizzle/selector_engine.js rename to src/selector_engine.js index f460f3352..9ec1266aa 100644 --- a/ext/sizzle/selector_engine.js +++ b/src/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_property = window.Sizzle; -//= require "repository/sizzle" +//= require "sizzle" Prototype.Selector = (function(engine) { function extend(elements) { diff --git a/ext/sizzle/repository/sizzle.js b/src/sizzle.js similarity index 100% rename from ext/sizzle/repository/sizzle.js rename to src/sizzle.js From a44a8db6aed068b0ece32481cf6821009da14e90 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 2 Dec 2009 21:41:52 +0100 Subject: [PATCH 070/502] Avoid automatically fetching the vendor/sizzle git submodule. --- Rakefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Rakefile b/Rakefile index 6d569862e..5e897652e 100755 --- a/Rakefile +++ b/Rakefile @@ -13,6 +13,7 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] + DEFAULT_SELECTOR_ENGINE = 'sizzle' %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') @@ -45,8 +46,8 @@ module PrototypeHelper require_sprockets load_path = [SRC_DIR] - if selector = options[:selector_engine] - load_path << get_selector_engine(selector) + if selector_path = get_selector_engine(options[:selector_engine]) + load_path << selector_path end secretary = Sprockets::Secretary.new( @@ -66,7 +67,7 @@ module PrototypeHelper :path => 'src', :source => file, :destination => temp_path, - :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle', + :selector_engine => ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE, :strip_comments => false ) rm_rf DOC_DIR @@ -99,8 +100,7 @@ module PrototypeHelper def self.get_selector_engine(name) submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) - ext_path = File.join(ROOT_DIR, "ext", name) - return ext_path if File.exist?(ext_path) + return if name == DEFAULT_SELECTOR_ENGINE get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) @@ -151,7 +151,7 @@ task :dist do PrototypeHelper.sprocketize( :path => 'src', :source => 'prototype.js', - :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle' + :selector_engine => ENV['SELECTOR_ENGINE'] || PrototypeHelper::DEFAULT_SELECTOR_ENGINE ) end From 7770ab99dc717b96e5b68e268aa20b081c5b9346 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 2 Dec 2009 15:14:18 -0600 Subject: [PATCH 071/502] Fix 'rake' without SELECTOR_ENGINE environment variable set --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 5e897652e..b6b3a370b 100755 --- a/Rakefile +++ b/Rakefile @@ -98,9 +98,9 @@ module PrototypeHelper end def self.get_selector_engine(name) + return if name == DEFAULT_SELECTOR_ENGINE || !name submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) - return if name == DEFAULT_SELECTOR_ENGINE get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) From 6bb309afb7b1254f275a12e029f9fcf9aec36e23 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 2 Dec 2009 15:57:58 -0600 Subject: [PATCH 072/502] Selector API change: replace Prototype.Selector.filter with Prototype.Selector.find, which works like the old Selector.findElement. --- src/dom/dom.js | 6 +++--- src/dom/event.js | 10 +++------- src/dom/selector.js | 22 ++++++++++++++++------ src/selector_engine.js | 9 ++------- test/unit/selector_engine_test.js | 11 ++++------- vendor/nwmatcher/selector_engine.js | 14 +------------- 6 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index c6e568877..6ab02419b 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -611,7 +611,7 @@ Element.Methods = { if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : - Prototype.Selector.filter(ancestors, expression)[index || 0]; + Prototype.Selector.find(ancestors, expression, index); }, /** @@ -647,7 +647,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Prototype.Selector.filter(element.previousSiblings(), expression)[index]; + return Prototype.Selector.find(element.previousSiblings(), expression, index); } else { return element.recursivelyCollect("previousSibling", index + 1)[index]; } @@ -669,7 +669,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Prototype.Selector.filter(element.nextSiblings(), expression)[index]; + return Prototype.Selector.find(element.nextSiblings(), expression, index); } else { var maximumLength = Object.isNumber(index) ? index + 1 : 1; return element.recursivelyCollect("nextSibling", index + 1)[index]; diff --git a/src/dom/event.js b/src/dom/event.js index 0bd9fd148..84fda31e1 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -149,14 +149,10 @@ function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; - while (element) { - if (Prototype.Selector.match(element, expression)) { - return Element.extend(element); - } - element = element.parentNode - } + var elements = [element].concat(element.ancestors()); + return Prototype.Selector.find(elements, expression, 0); } - + /** * Event.pointer(@event) -> Object * diff --git a/src/dom/selector.js b/src/dom/selector.js index 7e7470b15..c3276035d 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -42,15 +42,25 @@ window.$$ = function() { // Implementation provided by selector engine. /** - * Prototype.Selector.filter(elements, expression) -> [Element...] + * Prototype.Selector.find(elements, expression[, index]) -> Element * - elements (Enumerable): a collection of DOM elements. * - expression (String): A CSS selector. + # - index: Numeric index of the match to return, or 0 if omitted. * - * Filters the given collection of elements with `expression` and returns an - * array of extended [[Element]] objects. - * - * The only nodes returned will be those that match the given CSS selector. + * Filters the given collection of elements with `expression` and returns the + * first matching element (or the `index`th matching element if `index` is + * specified). **/ +if (!Prototype.Selector.find) { + Prototype.Selector.find = function(elements, expression, index) { + if (Object.isUndefined(index)) index = 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; -// Implementation provided by selector engine. + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } +} diff --git a/src/selector_engine.js b/src/selector_engine.js index 9ec1266aa..45ba30ce0 100644 --- a/src/selector_engine.js +++ b/src/selector_engine.js @@ -16,16 +16,11 @@ Prototype.Selector = (function(engine) { function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - - function filter(elements, selector) { - return extend(engine.matches(selector, elements)); - } - + return { engine: engine, select: select, - match: match, - filter: filter + match: match }; })(Sizzle); diff --git a/test/unit/selector_engine_test.js b/test/unit/selector_engine_test.js index dec966fc1..da428b01d 100644 --- a/test/unit/selector_engine_test.js +++ b/test/unit/selector_engine_test.js @@ -41,13 +41,10 @@ new Test.Unit.Runner({ this.assertEqual(false, Prototype.Selector.match(element, '.non_existent')); }, - testFilter: function() { + testFind: function() { var elements = document.getElementsByTagName('*'), - filtered = Prototype.Selector.filter(elements, '.test_class'); - - this.assert(Object.isArray(filtered)); - this.assertEqual(2, filtered.length); - this.assertEqual('test_div_parent', filtered[0].id); - this.assertEqual('test_div_child', filtered[1].id); + expression = '.test_class'; + this.assertEqual('test_div_parent', Prototype.Selector.find(elements, expression).id); + this.assertEqual('test_div_child', Prototype.Selector.find(elements, expression, 1).id); } }); \ No newline at end of file diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 5bc8ac4cd..2d55a148a 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -6,22 +6,10 @@ Prototype.Selector = (function(engine) { return engine.select(selector, scope || document, null, Element.extend); } - function filter(elements, selector) { - var results = [], element, i = 0; - while (element = elements[i++]) { - if (engine.match(element, selector)) { - Element.extend(element); - results.push(element); - } - } - return results; - } - return { engine: engine, select: select, - match: engine.match, - filter: filter + match: engine.match }; })(NW.Dom); From fa0dce2488634b6eca81196a95c4346398b21181 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Thu, 3 Dec 2009 00:31:35 -0600 Subject: [PATCH 073/502] Revert Event#findElement changes from 6bb309a --- src/dom/event.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 84fda31e1..8158eff0a 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -149,8 +149,12 @@ function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Prototype.Selector.find(elements, expression, 0); + while (element) { + if (Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; + } } /** From 6ebfdd51d52c1227d471676586880079511e142d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:23:29 -0600 Subject: [PATCH 074/502] Move a few methods from dom.js to layout.js. --- src/dom/dom.js | 29 -------------- src/dom/layout.js | 100 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 0f0e247c3..4ecb83f6e 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1041,35 +1041,6 @@ Element.Methods = { return element; }, - /** - * Element.getDimensions(@element) -> Object - * - * Finds the computed width and height of `element` and returns them as - * key/value pairs of an object. - **/ - getDimensions: function(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, /** * Element.makePositioned(@element) -> Element diff --git a/src/dom/layout.js b/src/dom/layout.js index 7f235f455..aae2c2941 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -639,8 +639,42 @@ **/ function measure(element, property) { return $(element).getLayout().get(property); + } + + /** + * Element.getDimensions(@element) -> Object + * + * Finds the computed width and height of `element` and returns them as + * key/value pairs of an object. + **/ + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.measure('width'), + height: layout.measure('height') + }; } + /** + * Element.getOffsetParent(@element) -> Element + * + * Returns `element`'s closest _positioned_ ancestor. If none is found, the + * `body` element is returned. + **/ + function getOffsetParent(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + /** * Element.cumulativeOffset(@element) -> Element.Offset * @@ -731,17 +765,79 @@ return new Element.Offset(valueL, valueT); } + /** + * Element.absolutize(@element) -> Element + * + * Turns `element` into an absolutely-positioned element _without_ + * changing its position in the page layout. + **/ + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), pOffset = + offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.get('layout'); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + } + + /** + * Element.relativize(@element) -> Element + * + * Turns `element` into a relatively-positioned element without changing + * its position in the page layout. + * + * Used to undo a call to [[Element.absolutize]]. + **/ + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + // Restore the original styles as captured by Element#absolutize. + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + Element.addMethods({ getLayout: getLayout, measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, cumulativeOffset: cumulativeOffset, positionedOffset: positionedOffset, cumulativeScrollOffset: cumulativeScrollOffset, - viewportOffset: viewportOffset + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize }); function isBody(element) { - return $w('BODY HTML').include(element.nodeName.toUpperCase()); + return element.nodeName.toUpperCase() === 'BODY'; } // If the browser supports the nonstandard `getBoundingClientRect` From b56e7e754b2b25a4a24ecb6bdb32ec943aceb29a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:23:47 -0600 Subject: [PATCH 075/502] Rewrite IE logic. --- src/dom/layout.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index aae2c2941..4234e0bd8 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -870,7 +870,8 @@ // When the BODY is the offsetParent, IE6 mistakenly reports the // parent as HTML. Use that as the litmus test to fix another // annoying IE6 quirk. - if (parent.nodeName.toUpperCase() === 'HTML') { + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { return positionedOffset(element); } From 2efeb8e20dc8cbd45aa4daa4887aa0d42ac226d7 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:24:19 -0600 Subject: [PATCH 076/502] Add Element.Layout#toCSS for exporting an object full of CSS key/value pairs. --- src/dom/layout.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 4234e0bd8..927fad5e2 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -89,6 +89,13 @@ return element; }; } + + // Converts the layout hash property names back to the CSS equivalents. + // For now, only the border properties differ. + function cssNameFor(key) { + if (key.includes('border')) return key + '-width'; + return key; + } /** @@ -341,7 +348,39 @@ var value = COMPUTATIONS[property].call(this, this.element); this._set(property, value); return value; - } + }, + + /** + * Element.Layout#toCSS([keys...]) -> Object + * + * Converts the layout hash to a plain object of CSS property/value + * pairs, optionally including only the given keys. + * + * Useful for passing layout properties to [[Element.setStyle]]. + **/ + toCSS: function() { + var keys = []; + for (var i = 0, j, argKeys, l = arguments.length; i < l; i++) { + argKeys = arguments[i].split(' '); + for (j = 0; j < argKeys.length; j++) { + keys.push(argKeys[j]); + } + } + if (keys.length === 0) keys = Element.Layout.PROPERTIES; + var css = {}; + keys.each( function(key) { + // Key needs to be a valid Element.Layout property... + if (!Element.Layout.PROPERTIES.include(key)) return; + // ...but not a composite property. + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + // Unless the value is null, add 'px' to the end and add it to the + // returned object. + if (value) css[cssNameFor(key)] = value + 'px'; + }); + return css; + } }); Object.extend(Element.Layout, { From 5149c1b0dfae9c52904ce1b2840cf91027f1d314 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:24:45 -0600 Subject: [PATCH 077/502] Add a couple more layout tests. --- test/unit/fixtures/layout.html | 25 +++++++++++++++++++++++++ test/unit/layout_test.js | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html index 918e20c6c..5921e38ee 100644 --- a/test/unit/fixtures/layout.html +++ b/test/unit/fixtures/layout.html @@ -1,3 +1,5 @@ + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    @@ -16,6 +18,7 @@ +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    @@ -30,6 +33,7 @@ } +
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. @@ -52,6 +56,7 @@ } +
    @@ -79,6 +84,7 @@ } +
    @@ -102,4 +108,23 @@ top: 30px; right: 10%; } + + + +
    +
    + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + \ No newline at end of file diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index eec7187b3..46180c580 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -78,5 +78,12 @@ new Test.Unit.Runner({ this.assertEqual(60, layout.get('right'), 'right (percentage value)'); this.assertEqual(340, layout.get('left'), 'left'); + }, + + 'test positioning on absolutely-positioned element with top=0 and left=0': function() { + var layout = $('box6').getLayout(); + + this.assertEqual(0, layout.get('top'), 'top'); + this.assertIdentical($('box6_parent'), $('box6').getOffsetParent()); } }); \ No newline at end of file From 0dd600974cfdd4043e9b20368cb4c018a46e584a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:27:49 -0600 Subject: [PATCH 078/502] Add documentation for event key codes. (cherry picked from commit 93bd2048e2c106ecc309292be358ef398df05984) --- src/dom/event.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dom/event.js b/src/dom/event.js index 8158eff0a..f7a481758 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -25,7 +25,10 @@ * browsers. * * `Event` also provides a standardized list of key codes you can use with - * keyboard-related events. + * keyboard-related events, including `KEY_BACKSPACE`, `KEY_TAB`, + * `KEY_RETURN`, `KEY_ESC`, `KEY_LEFT`, `KEY_UP`, `KEY_RIGHT`, `KEY_DOWN`, + * `KEY_DELETE`, `KEY_HOME`, `KEY_END`, `KEY_PAGEUP`, `KEY_PAGEDOWN` and + * `KEY_INSERT`. * * The functions you're most likely to use a lot are [[Event.observe]], * [[Event.element]] and [[Event.stop]]. If your web app uses custom events, From c028935279dbbc42b2c9e0451103a4e80f5675ae Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:28:31 -0600 Subject: [PATCH 079/502] Add note about mouseenter/leave support to docs. (cherry picked from commit 1c4c2005341ef587dca7eacc8d347367e3eb7ef7) --- src/dom/event.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dom/event.js b/src/dom/event.js index f7a481758..d52c1da9b 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -415,6 +415,7 @@ * * Prototype handles cleaning up the handler when leaving the page (important for MSIE memory * leak prevention). * * `observe` makes it possible to stop observing the event easily via [[Event.stopObserving]]. + * * Adds support for `mouseenter` / `mouseleave` in all browsers. * * Although you can use `Event.observe` directly and there are times when that's the most * convenient or direct way, it's more common to use its alias [[Element#observe]]. These two From 83b0c153d3e3fc9c6421d127c9e52eb538c0c85c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:29:40 -0600 Subject: [PATCH 080/502] Add examples for String#gsub and String#sub. (cherry picked from commit 591b25eb2604953754f08c40e9ef99791a6bb51b) --- src/lang/string.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lang/string.js b/src/lang/string.js index a7c033457..e581f6322 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -42,6 +42,11 @@ Object.extend(String.prototype, (function() { * Returns the string with every occurence of a given pattern replaced by either * a regular string, the returned value of a function or a [[Template]] string. * The pattern can be a string or a regular expression. + * + *
    Example
    + * + * ""hello".gsub(/([aeiou])/, '<#{1}>'); + * // => "hll" **/ function gsub(pattern, replacement) { var result = '', source = this, match; @@ -73,6 +78,11 @@ Object.extend(String.prototype, (function() { * Returns a string with the first count occurrences of pattern replaced by either * a regular string, the returned value of a function or a [[Template]] string. * The pattern can be a string or a regular expression. + * + *
    Example
    + * + * "20091201".sub(/^(\d{4})(\d{2})(\d{2})$/, "#{1}-#{2}-#{3}"); + * // => "2009-12-01" **/ function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); From b8121bf80bf3699740aca39f5acd78b3f31ee596 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 30 Oct 2009 04:19:17 -0500 Subject: [PATCH 081/502] Revised pass on `Element.Layout` and `Element.Offset` classes. Unit tests are incomplete. --- src/dom.js | 1 + src/dom/layout.js | 575 +++++++++++++++++++++++++++++++++ test/unit/fixtures/layout.html | 105 ++++++ test/unit/layout_test.js | 75 +++++ 4 files changed, 756 insertions(+) create mode 100644 src/dom/layout.js create mode 100644 test/unit/fixtures/layout.html create mode 100644 test/unit/layout_test.js diff --git a/src/dom.js b/src/dom.js index 1fb0187d1..b28bfb822 100644 --- a/src/dom.js +++ b/src/dom.js @@ -28,6 +28,7 @@ //= require "dom/dom" //= require +//= require "dom/layout" //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/src/dom/layout.js b/src/dom/layout.js new file mode 100644 index 000000000..2547cd434 --- /dev/null +++ b/src/dom/layout.js @@ -0,0 +1,575 @@ + + +(function() { + + // Converts a CSS percentage value to a decimal. + // Ex: toDecimal("30%"); // -> 0.3 + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + // Can be called like this: + // getPixelValue("11px"); + // Or like this: + // getPixelValue(someElement, 'paddingTop'); + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; + } + + // Non-IE browsers will always return pixels. + if ((/^\d+(px)?$/i).test(value)) { + return window.parseInt(value, 10); + } + + // When IE gives us something other than a pixel value, this technique + // (invented by Dean Edwards) will convert it to pixels. + if (element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; + + return value; + } + + // For other browsers, we have to do a bit of work. + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } + + // If we get this far, we should probably give up. + return null; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + /** + * class Element.Layout < Hash + * + * A set of key/value pairs representing measurements of various + * dimensions of an element. + **/ + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + // The 'preCompute' boolean tells us whether we should fetch all values + // at once. If so, we should do setup/teardown only once. We set a flag + // so that we can ignore calls to `_begin` and `_end` elsewhere. + if (preCompute) { + this._preComputing = true; + this._begin(); + } + Element.Layout.PROPERTIES.each( function(property) { + if (preCompute) { + this._compute(property); + } else { + this._set(property, null); + } + }, this); + if (preCompute) { + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { + throw "Cannot set a composite property."; + } + + return this._set(property, toCSSPixels(value)); + }, + + get: function($super, property) { + // Try to fetch from the cache. + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + // `_begin` and `_end` are two functions that are called internally + // before and after any measurement is done. In certain conditions (e.g., + // when hidden), elements need a "preparation" phase that ensures + // accuracy of measurements. + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + // Remember the original values for some styles we're going to alter. + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + // We store them so that the `_end` function can retrieve them later. + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + var layout = element.getLayout(); + element.setStyle({ + position: 'absolute', + visibility: 'visible', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + // If the element's width is the same both before and after + // we set absolute positioning, that means: + // (a) it was already absolutely-positioned; or + // (b) it has an explicitly-set width, instead of width: auto. + // Either way, it means the element is the width it needs to be + // in order to report an accurate height. + newWidth = window.parseInt(width, 10); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = window.parseInt(width, 10); + } else { + // If not, that means the element's width depends upon the width of + // its parent. + var parent = element.up(), pLayout = parent.getLayout(); + + newWidth = pLayout.get('width') - + layout.get('margin-left') - + layout.get('border-left') - + layout.get('padding-left') - + layout.get('padding-right') - + layout.get('border-right') - + layout.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + // The element is now ready for measuring. + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + + var value = COMPUTATIONS[property].call(this, this.element); + this._set(property, value); + return value; + } + }); + + Object.extend(Element.Layout, { + // All measurable properties. + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + // Sums of other properties. Can be read but not written. + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + return element.offsetHeight; + }, + + 'border-box-width': function(element) { + return element.offsetWidth; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + return getPixelValue(element, 'top'); + }, + + 'bottom': function(element) { + return getPixelValue(element, 'bottom'); + }, + + 'left': function(element) { + return getPixelValue(element, 'left'); + }, + + 'right': function(element) { + return getPixelValue(element, 'right'); + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return element.clientTop || + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return element.clientBottom || + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return element.clientLeft || + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return element.clientRight || + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); + } + } + }); + + /** + * class Element.Offset + * + * A representation of the top- and left-offsets of an element relative to + * another. + **/ + Element.Offset = Class.create({ + /** + * new Element.Offset(left, top) + * + * Instantiates an [[Element.Offset]]. You shouldn't need to call this + * directly. + **/ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + // Act like an array. + this[0] = this.left; + this[1] = this.top; + }, + + /** + * Element.Offset#relativeTo(offset) -> Element.Offset + * - offset (Element.Offset): Another offset to compare to. + * + * Returns a new [[Element.Offset]] with its origin at the given + * `offset`. Useful for determining an element's distance from another + * arbitrary element. + **/ + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); + }, + + /** + * Element.Offset#inspect() -> String + **/ + inspect: function() { + return "# Array + **/ + toArray: function() { + return [this.left, this.top]; + } + }); + + /** + * Element.getLayout(@element) -> Element.Layout + * + * Returns an instance of [[Element.Layout]] for measuring an element's + * dimensions. + * + * Note that this method returns a _new_ `Element.Layout` object each time + * it's called. If you want to take advantage of measurement caching, + * retain a reference to one `Element.Layout` object, rather than calling + * `Element.getLayout` whenever you need a measurement. You should call + * `Element.getLayout` again only when the values in an existing + * `Element.Layout` object have become outdated. + **/ + function getLayout(element) { + return new Element.Layout(element); + } + + /** + * Element.measure(@element, property) -> Number + * + * Gives the pixel value of `element`'s dimension specified by + * `property`. + * + * Useful for one-off measurements of elements. If you find yourself + * calling this method frequently over short spans of code, you might want + * to call [[Element.getLayout]] and operate on the [[Element.Layout]] + * object itself (thereby taking advantage of measurement caching). + **/ + function measure(element, property) { + return $(element).getLayout().get(property); + } + + /** + * Element.cumulativeOffset(@element) -> Element.Offset + * + * Returns the offsets of `element` from the top left corner of the + * document. + **/ + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.positionedOffset(@element) -> Element.Offset + * + * Returns `element`'s offset relative to its closest positioned ancestor + * (the element that would be returned by [[Element.getOffsetParent]]). + **/ + function positionedOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.cumulativeScrollOffset(@element) -> Element.Offset + * + * Calculates the cumulative scroll offset of an element in nested + * scrolling containers. + **/ + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + /** + * Element.viewportOffset(@element) -> Array + * + * Returns the X/Y coordinates of element relative to the viewport. + **/ + function viewportOffset(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + var tagName = element.tagName, O = Prototype.Browser.Opera; + do { + if (!O || tagName && tagName.toUpperCase() === 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset + }); + + // If the browser supports the nonstandard `getBoundingClientRect` + // (currently only IE and Firefox), it becomes far easier to obtain + // true offsets. + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + var rect = element.getBoundingClientRect(); + return new Element.Offset(rect.left, rect.top); + }, + + cumulativeOffset: function(element) { + element = $(element); + var docOffset = $(document.documentElement).viewportOffset(), + elementOffset = element.viewportOffset(); + return elementOffset.relativeTo(docOffset); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + var isBody = (parent.nodeName.toUpperCase() === 'BODY'); + var eOffset = element.viewportOffset(), + pOffset = isBody ? viewportOffset(parent) : parent.viewportOffset(); + return eOffset.relativeTo(pOffset); + } + }); + } +})(); \ No newline at end of file diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html new file mode 100644 index 000000000..918e20c6c --- /dev/null +++ b/test/unit/fixtures/layout.html @@ -0,0 +1,105 @@ +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    +
    + + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    +
    + + + +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + \ No newline at end of file diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js new file mode 100644 index 000000000..3cbb0518c --- /dev/null +++ b/test/unit/layout_test.js @@ -0,0 +1,75 @@ +function isDisplayed(element) { + var originalElement = element; + + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; +} + +new Test.Unit.Runner({ + setup: function() { + }, + + 'test layout on absolutely-positioned elements': function() { + var layout = $('box1').getLayout(); + + this.assertEqual(242, layout.get('width'), 'width' ); + this.assertEqual(555, layout.get('height'), 'height'); + + this.assertEqual(3, layout.get('border-left'), 'border-left'); + this.assertEqual(10, layout.get('padding-top'), 'padding-top'); + this.assertEqual(1020, layout.get('top'), 'top'); + + this.assertEqual(25, layout.get('left'), 'left'); + }, + + 'test layout on elements with display: none and exact width': function() { + var layout = $('box2').getLayout(); + + this.assertEqual(500, layout.get('width'), 'width'); + this.assertEqual(3, layout.get('border-right'), 'border-right'); + this.assertEqual(10, layout.get('padding-bottom'), 'padding-bottom'); + }, + + 'test layout on elements with display: none and width: auto': function() { + var layout = $('box3').getLayout(); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + + + this.assertEqual(364, layout.get('width'), 'width'); + this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); + this.assertEqual(3, layout.get('border-right'), 'border-top'); + this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); + }, + + 'test layout on elements with display: none ancestors': function() { + var layout = $('box4').getLayout(); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box4')), 'box should still be hidden'); + + // Width and height values are nonsensical for deeply-hidden elements. + this.assertEqual(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); + this.assertEqual(0, layout.get('margin-box-height'), 'height of a deeply-hidden element should be 0'); + + // But we can still get meaningful values for other measurements. + this.assertEqual(0, layout.get('border-right'), 'border-top'); + this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); + }, + + 'test positioning on absolutely-positioned elements': function() { + var layout = $('box5').getLayout(); + + this.assertEqual(30, layout.get('top'), 'top'); + this.assertEqual(60, layout.get('right'), 'right (percentage value)'); + + this.assertNull(layout.get('left'), 'left (should be null because none was set)'); + } +}); \ No newline at end of file From 04c3dfd1a934da25692e00854303e31b28a9786b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 20:03:17 -0600 Subject: [PATCH 082/502] Update layout code. Roughly compatible with IE 6-8... so far. --- src/dom/layout.js | 32 ++++++++++++++++---------------- test/unit/layout_test.js | 13 +++++++++---- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 2547cd434..204fcbca8 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -30,7 +30,7 @@ // When IE gives us something other than a pixel value, this technique // (invented by Dean Edwards) will convert it to pixels. - if (element.runtimeStyle) { + if (/\d/.test(value) && element.runtimeStyle) { var style = element.style.left, rStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = value || 0; @@ -38,7 +38,7 @@ element.style.left = style; element.runtimeStyle.left = rStyle; - return value; + return value; } // For other browsers, we have to do a bit of work. @@ -57,7 +57,7 @@ } // If we get this far, we should probably give up. - return null; + return 0; } function toCSSPixels(number) { @@ -154,10 +154,9 @@ var position = element.getStyle('position'), width = element.getStyle('width'); - var layout = element.getLayout(); element.setStyle({ position: 'absolute', - visibility: 'visible', + visibility: 'hidden', display: 'block' }); @@ -177,15 +176,16 @@ } else { // If not, that means the element's width depends upon the width of // its parent. - var parent = element.up(), pLayout = parent.getLayout(); + var parent = element.parentNode, pLayout = $(parent).getLayout(); + newWidth = pLayout.get('width') - - layout.get('margin-left') - - layout.get('border-left') - - layout.get('padding-left') - - layout.get('padding-right') - - layout.get('border-right') - - layout.get('margin-right'); + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); } element.setStyle({ width: newWidth + 'px' }); @@ -333,22 +333,22 @@ }, 'border-top': function(element) { - return element.clientTop || + return Object.isNumber(element.clientTop) ? element.clientTop : getPixelValue(element, 'borderTopWidth'); }, 'border-bottom': function(element) { - return element.clientBottom || + return Object.isNumber(element.clientBottom) ? element.clientBottom : getPixelValue(element, 'borderBottomWidth'); }, 'border-left': function(element) { - return element.clientLeft || + return Object.isNumber(element.clientLeft) ? element.clientLeft : getPixelValue(element, 'borderLeftWidth'); }, 'border-right': function(element) { - return element.clientRight || + return Object.isNumber(element.clientRight) ? element.clientRight : getPixelValue(element, 'borderRightWidth'); }, diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 3cbb0518c..5b057925a 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -31,29 +31,34 @@ new Test.Unit.Runner({ 'test layout on elements with display: none and exact width': function() { var layout = $('box2').getLayout(); + this.assert(!isDisplayed($('box3')), 'box should be hidden'); + this.assertEqual(500, layout.get('width'), 'width'); this.assertEqual(3, layout.get('border-right'), 'border-right'); this.assertEqual(10, layout.get('padding-bottom'), 'padding-bottom'); + + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test layout on elements with display: none and width: auto': function() { var layout = $('box3').getLayout(); - // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + this.assert(!isDisplayed($('box3')), 'box should be hidden'); - this.assertEqual(364, layout.get('width'), 'width'); this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); this.assertEqual(3, layout.get('border-right'), 'border-top'); this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test layout on elements with display: none ancestors': function() { var layout = $('box4').getLayout(); // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box4')), 'box should still be hidden'); + this.assert(!isDisplayed($('box4')), 'box should be hidden'); // Width and height values are nonsensical for deeply-hidden elements. this.assertEqual(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); From c6a96c364707c93b47640620cf37e15758870e39 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:52:07 -0600 Subject: [PATCH 083/502] A bunch of fixes for offsets. --- src/dom/layout.js | 57 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 204fcbca8..854f56622 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -79,6 +79,18 @@ return true; } + var hasLayout = Prototype.K; + + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + /** * class Element.Layout < Hash * @@ -411,7 +423,14 @@ * Element.Offset#inspect() -> String **/ inspect: function() { - return "#".interpolate(this); + }, + + /** + * Element.Offset#toString() -> String + **/ + toString: function() { + return "[#{left}, #{top}]".interpolate(this); }, /** @@ -477,17 +496,24 @@ * (the element that would be returned by [[Element.getOffsetParent]]). **/ function positionedOffset(element) { + // Account for the margin of the element. + var layout = element.getLayout(); + var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; + if (isBody(element)) break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + return new Element.Offset(valueL, valueT); } @@ -544,6 +570,10 @@ viewportOffset: viewportOffset }); + function isBody(element) { + return $w('BODY HTML').include(element.nodeName.toUpperCase()); + } + // If the browser supports the nonstandard `getBoundingClientRect` // (currently only IE and Firefox), it becomes far easier to obtain // true offsets. @@ -565,11 +595,26 @@ positionedOffset: function(element) { element = $(element); var parent = element.getOffsetParent(); - var isBody = (parent.nodeName.toUpperCase() === 'BODY'); + + // When the BODY is the offsetParent, IE6 mistakenly reports the + // parent as HTML. Use that as the litmus test to fix another + // annoying IE6 quirk. + if (parent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); + } + var eOffset = element.viewportOffset(), - pOffset = isBody ? viewportOffset(parent) : parent.viewportOffset(); - return eOffset.relativeTo(pOffset); + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + // Account for the margin of the element. + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); } - }); + }); } })(); \ No newline at end of file From 27da3a906553b316f068f931749e696d6ca35091 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:52:19 -0600 Subject: [PATCH 084/502] Tweaks to unit tests. --- test/unit/layout_test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 5b057925a..eec7187b3 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -57,7 +57,6 @@ new Test.Unit.Runner({ 'test layout on elements with display: none ancestors': function() { var layout = $('box4').getLayout(); - // Ensure that we cleaned up after ourselves. this.assert(!isDisplayed($('box4')), 'box should be hidden'); // Width and height values are nonsensical for deeply-hidden elements. @@ -67,6 +66,9 @@ new Test.Unit.Runner({ // But we can still get meaningful values for other measurements. this.assertEqual(0, layout.get('border-right'), 'border-top'); this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, 'test positioning on absolutely-positioned elements': function() { @@ -75,6 +77,6 @@ new Test.Unit.Runner({ this.assertEqual(30, layout.get('top'), 'top'); this.assertEqual(60, layout.get('right'), 'right (percentage value)'); - this.assertNull(layout.get('left'), 'left (should be null because none was set)'); + this.assertEqual(340, layout.get('left'), 'left'); } }); \ No newline at end of file From c2c47da6aa4e41d2cb45255ad8f3c95eb26f8b44 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 2 Nov 2009 23:54:25 -0600 Subject: [PATCH 085/502] Optimize retrieving of top|left|right|bottom properties. Add some documentation. Disable setting of properties for now. --- src/dom/layout.js | 188 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 10 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 854f56622..3152ed6b9 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -96,8 +96,115 @@ * * A set of key/value pairs representing measurements of various * dimensions of an element. + * + *

    Overview

    + * + * The `Element.Layout` class is a specialized way to measure elements. + * It helps mitigate: + * + * * The convoluted steps often needed to get common measurements for + * elements. + * * The tendency of browsers to report measurements in non-pixel units. + * * The quirks that lead some browsers to report inaccurate measurements. + * * The difficulty of measuring elements that are hidden. + * + *

    Usage

    + * + * Instantiate an `Element.Layout` class by passing an element into the + * constructor: + * + * var layout = new Element.Layout(someElement); + * + * You can also use [[Element.getLayout]], if you prefer. + * + * Once you have a layout object, retrieve properties using [[Hash]]'s + * familiar `get` and `set` syntax. + * + * layout.get('width'); //-> 400 + * layout.get('top'); //-> 180 + * + * The following are the CSS-related properties that can be retrieved. + * Nearly all of them map directly to their property names in CSS. (The + * only exception is for borders — e.g., `border-width` instead of + * `border-left-width`.) + * + * * `height` + * * `width` + * * `top` + * * `left` + * * `right` + * * `bottom` + * * `border-left` + * * `border-right` + * * `border-top` + * * `border-bottom` + * * `padding-left` + * * `padding-right` + * * `padding-top` + * * `padding-bottom` + * * `margin-top` + * * `margin-bottom` + * * `margin-left` + * * `margin-right` + * + * In addition, these "composite" properties can be retrieved: + * + * * `padding-box-width` (width of the content area, from the beginning of + * the left padding to the end of the right padding) + * * `padding-box-height` (height of the content area, from the beginning + * of the top padding to the end of the bottom padding) + * * `border-box-width` (width of the content area, from the outer edge of + * the left border to the outer edge of the right border) + * * `border-box-height` (height of the content area, from the outer edge + * of the top border to the outer edge of the bottom border) + * * `margin-box-width` (width of the content area, from the beginning of + * the left margin to the end of the right margin) + * * `margin-box-height` (height of the content area, from the beginning + * of the top margin to the end of the bottom margin) + * + *

    Caching

    + * + * Because these properties can be costly to retrieve, `Element.Layout` + * behaves differently from an ordinary [[Hash]]. + * + * First: by default, values are "lazy-loaded" — they aren't computed + * until they're retrieved. To measure all properties at once, pass + * a second argument into the constructor: + * + * var layout = new Element.Layout(someElement, true); + * + * Second: once a particular value is computed, it's cached. Asking for + * the same property again will return the original value without + * re-computation. This means that **an instance of `Element.Layout` + * becomes stale when the element's dimensions change**. When this + * happens, obtain a new instance. + * + *

    Hidden elements

    + * + * Because it's a common case to want the dimensions of a hidden element + * (e.g., for animations), it's possible to measure elements that are + * hidden with `display: none`. + * + * However, **it's only possible to measure a hidden element if its parent + * is visible**. If its parent (or any other ancestor) is hidden, any + * width and height measurements will return `0`, as will measurements for + * `top|bottom|left|right`. + * **/ Element.Layout = Class.create(Hash, { + /** + * new Element.Layout(element[, preCompute]) + * - element (Element): The element to be measured. + * - preCompute (Boolean): Whether to compute all values at once. + * + * Declare a new layout hash. + * + * The `preCompute` argument determines whether measurements will be + * lazy-loaded or not. If you plan to use many different measurements, + * it's often more performant to pre-compute, as it minimizes the + * amount of overhead needed to measure. If you need only one or two + * measurements, it's probably not worth it. + **/ initialize: function($super, element, preCompute) { $super(); this.element = $(element); @@ -125,14 +232,25 @@ return Hash.prototype.set.call(this, property, value); }, + + // TODO: Investigate. set: function(property, value) { - if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { - throw "Cannot set a composite property."; - } - - return this._set(property, toCSSPixels(value)); + throw "Properties of Element.Layout are read-only."; + // if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { + // throw "Cannot set a composite property."; + // } + // + // return this._set(property, toCSSPixels(value)); }, + /** + * Element.Layout#get(property) -> Number + * - property (String): One of the properties defined in + * [[Element.Layout.PROPERTIES]]. + * + * Retrieve the measurement specified by `property`. Will throw an error + * if the property is invalid. + **/ get: function($super, property) { // Try to fetch from the cache. var value = $super(property); @@ -228,9 +346,20 @@ Object.extend(Element.Layout, { // All measurable properties. + /** + * Element.Layout.PROPERTIES = Array + * + * A list of all measurable properties. + **/ PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), - // Sums of other properties. Can be read but not written. + /** + * Element.Layout.COMPOSITE_PROPERTIES = Array + * + * A list of all composite properties. Composite properties don't map + * directly to CSS properties — they're combinations of other + * properties. + **/ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), COMPUTATIONS: { @@ -313,19 +442,37 @@ }, 'top': function(element) { - return getPixelValue(element, 'top'); + var offset = element.positionedOffset(); + return offset.top; }, 'bottom': function(element) { - return getPixelValue(element, 'bottom'); + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + // + // return getPixelValue(element, 'bottom'); }, 'left': function(element) { - return getPixelValue(element, 'left'); + var offset = element.positionedOffset(); + return offset.left; }, 'right': function(element) { - return getPixelValue(element, 'right'); + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + // + // return getPixelValue(element, 'right'); }, 'padding-top': function(element) { @@ -382,6 +529,27 @@ } }); + // An easier way to compute right and bottom offsets. + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + /** * class Element.Offset * From 7015eb13670701f7b8f7c047770db46fc08d2455 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 10:56:01 -0600 Subject: [PATCH 086/502] Fix Element.viewportOffset on nested elements in Opera < 9.5 and remove browser sniff. (cherry picked from commit 0d2d18fb7297fb945ca6983b843c5bcf367c721c) --- src/dom/layout.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 3152ed6b9..d0d4dca13 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -707,21 +707,23 @@ * Returns the X/Y coordinates of element relative to the viewport. **/ function viewportOffset(forElement) { - var valueT = 0, valueL = 0; + var valueT = 0, valueL = 0, docBody = document.body; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix - if (element.offsetParent == document.body && + if (element.offsetParent == docBody && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); - element = forElement; - var tagName = element.tagName, O = Prototype.Browser.Opera; + element = forElement; do { - if (!O || tagName && tagName.toUpperCase() === 'BODY') { + // Opera < 9.5 sets scrollTop/Left on both HTML and BODY elements. + // Other browsers set it only on the HTML element. The BODY element + // can be skipped since its scrollTop/Left should always be 0. + if (element != docBody) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } From 2892a74d6383e29a2eccd0bfc928a4b26b138a3a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 10:58:45 -0600 Subject: [PATCH 087/502] Fix incorrect offset in Element.viewportOffset on IE < 8. (cherry picked from commit 3afb0002cbd31726187338c5119657b76111f80c) --- src/dom/layout.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index d0d4dca13..444da63d0 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -751,8 +751,13 @@ Element.addMethods({ viewportOffset: function(element) { element = $(element); - var rect = element.getBoundingClientRect(); - return new Element.Offset(rect.left, rect.top); + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + // The HTML element on IE < 8 has a 2px border by default, giving + // an incorrect offset. We correct this by subtracting clientTop + // and clientLeft. + return new Element.Offset(rect.top - docEl.clientTop, + rect.left - docEl.clientLeft); }, cumulativeOffset: function(element) { From 4147f972f39107b41bd4cb32a0a1e74904272789 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 6 Nov 2009 14:47:38 -0600 Subject: [PATCH 088/502] Fix bug introduced in rewrite of Nick's patch. I'm an idiot. --- src/dom/layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 444da63d0..7f235f455 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -756,8 +756,8 @@ // The HTML element on IE < 8 has a 2px border by default, giving // an incorrect offset. We correct this by subtracting clientTop // and clientLeft. - return new Element.Offset(rect.top - docEl.clientTop, - rect.left - docEl.clientLeft); + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); }, cumulativeOffset: function(element) { From 507c1feb21b76fd0e144ae8ad656da6f730548dd Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:23:29 -0600 Subject: [PATCH 089/502] Move a few methods from dom.js to layout.js. --- src/dom/dom.js | 30 -------------- src/dom/layout.js | 100 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 32 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 6ab02419b..2275f39e2 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1064,36 +1064,6 @@ Element.Methods = { return element; }, - /** - * Element.getDimensions(@element) -> Object - * - * Finds the computed width and height of `element` and returns them as - * key/value pairs of an object. - **/ - getDimensions: function(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style, - originalVisibility = els.visibility, - originalPosition = els.position, - originalDisplay = els.display; - els.visibility = 'hidden'; - if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth, - originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - /** * Element.makePositioned(@element) -> Element * diff --git a/src/dom/layout.js b/src/dom/layout.js index 7f235f455..aae2c2941 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -639,8 +639,42 @@ **/ function measure(element, property) { return $(element).getLayout().get(property); + } + + /** + * Element.getDimensions(@element) -> Object + * + * Finds the computed width and height of `element` and returns them as + * key/value pairs of an object. + **/ + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.measure('width'), + height: layout.measure('height') + }; } + /** + * Element.getOffsetParent(@element) -> Element + * + * Returns `element`'s closest _positioned_ ancestor. If none is found, the + * `body` element is returned. + **/ + function getOffsetParent(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); + } + } + + return $(document.body); + } + + /** * Element.cumulativeOffset(@element) -> Element.Offset * @@ -731,17 +765,79 @@ return new Element.Offset(valueL, valueT); } + /** + * Element.absolutize(@element) -> Element + * + * Turns `element` into an absolutely-positioned element _without_ + * changing its position in the page layout. + **/ + function absolutize(element) { + element = $(element); + + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } + + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), pOffset = + offsetParent.viewportOffset(); + + var offset = eOffset.relativeTo(pOffset); + var layout = element.get('layout'); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + } + + /** + * Element.relativize(@element) -> Element + * + * Turns `element` into a relatively-positioned element without changing + * its position in the page layout. + * + * Used to undo a call to [[Element.absolutize]]. + **/ + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + // Restore the original styles as captured by Element#absolutize. + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + Element.addMethods({ getLayout: getLayout, measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, cumulativeOffset: cumulativeOffset, positionedOffset: positionedOffset, cumulativeScrollOffset: cumulativeScrollOffset, - viewportOffset: viewportOffset + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize }); function isBody(element) { - return $w('BODY HTML').include(element.nodeName.toUpperCase()); + return element.nodeName.toUpperCase() === 'BODY'; } // If the browser supports the nonstandard `getBoundingClientRect` From 74f05335f2fddd0f298cef098ec4d79ddea959a0 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:23:47 -0600 Subject: [PATCH 090/502] Rewrite IE logic. --- src/dom/layout.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index aae2c2941..4234e0bd8 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -870,7 +870,8 @@ // When the BODY is the offsetParent, IE6 mistakenly reports the // parent as HTML. Use that as the litmus test to fix another // annoying IE6 quirk. - if (parent.nodeName.toUpperCase() === 'HTML') { + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { return positionedOffset(element); } From 778f2c70dcd09dd3d208cd4fedb524e9913b7e92 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:24:19 -0600 Subject: [PATCH 091/502] Add Element.Layout#toCSS for exporting an object full of CSS key/value pairs. --- src/dom/layout.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 4234e0bd8..927fad5e2 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -89,6 +89,13 @@ return element; }; } + + // Converts the layout hash property names back to the CSS equivalents. + // For now, only the border properties differ. + function cssNameFor(key) { + if (key.includes('border')) return key + '-width'; + return key; + } /** @@ -341,7 +348,39 @@ var value = COMPUTATIONS[property].call(this, this.element); this._set(property, value); return value; - } + }, + + /** + * Element.Layout#toCSS([keys...]) -> Object + * + * Converts the layout hash to a plain object of CSS property/value + * pairs, optionally including only the given keys. + * + * Useful for passing layout properties to [[Element.setStyle]]. + **/ + toCSS: function() { + var keys = []; + for (var i = 0, j, argKeys, l = arguments.length; i < l; i++) { + argKeys = arguments[i].split(' '); + for (j = 0; j < argKeys.length; j++) { + keys.push(argKeys[j]); + } + } + if (keys.length === 0) keys = Element.Layout.PROPERTIES; + var css = {}; + keys.each( function(key) { + // Key needs to be a valid Element.Layout property... + if (!Element.Layout.PROPERTIES.include(key)) return; + // ...but not a composite property. + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + // Unless the value is null, add 'px' to the end and add it to the + // returned object. + if (value) css[cssNameFor(key)] = value + 'px'; + }); + return css; + } }); Object.extend(Element.Layout, { From 58f82feb1c8cc2f40aba5afe97a6a38e0acc3cb9 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 4 Dec 2009 18:24:45 -0600 Subject: [PATCH 092/502] Add a couple more layout tests. --- test/unit/fixtures/layout.html | 25 +++++++++++++++++++++++++ test/unit/layout_test.js | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html index 918e20c6c..5921e38ee 100644 --- a/test/unit/fixtures/layout.html +++ b/test/unit/fixtures/layout.html @@ -1,3 +1,5 @@ + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    @@ -16,6 +18,7 @@ +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    @@ -30,6 +33,7 @@ } +
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. @@ -52,6 +56,7 @@ } +
    @@ -79,6 +84,7 @@ } +
    @@ -102,4 +108,23 @@ top: 30px; right: 10%; } + + + +
    +
    + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + \ No newline at end of file diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index eec7187b3..46180c580 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -78,5 +78,12 @@ new Test.Unit.Runner({ this.assertEqual(60, layout.get('right'), 'right (percentage value)'); this.assertEqual(340, layout.get('left'), 'left'); + }, + + 'test positioning on absolutely-positioned element with top=0 and left=0': function() { + var layout = $('box6').getLayout(); + + this.assertEqual(0, layout.get('top'), 'top'); + this.assertIdentical($('box6_parent'), $('box6').getOffsetParent()); } }); \ No newline at end of file From d6ed7efe94d9dfbba7133edaecca3c5d28d11b81 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 17 Dec 2009 14:52:27 -0600 Subject: [PATCH 093/502] Tweak the PDoc templates. --- templates/html/assets/images/menu-flap.png | Bin 0 -> 1137 bytes .../html/assets/images/search-background.png | Bin 0 -> 295 bytes .../html/assets/images/section-background.png | Bin 0 -> 296 bytes templates/html/assets/javascripts/tabs.js | 506 ++++++++++++++++++ 4 files changed, 506 insertions(+) create mode 100644 templates/html/assets/images/menu-flap.png create mode 100644 templates/html/assets/images/search-background.png create mode 100644 templates/html/assets/images/section-background.png create mode 100644 templates/html/assets/javascripts/tabs.js diff --git a/templates/html/assets/images/menu-flap.png b/templates/html/assets/images/menu-flap.png new file mode 100644 index 0000000000000000000000000000000000000000..4424406b054a189d80682e9e908c1aaae0aeae11 GIT binary patch literal 1137 zcmV-%1djWOP)P002@50{{R3mZ*>Z0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzs!&W+MPGxtN@b7#|Nm!+ywBC%pR2t; zR(eZef?k5S^Yr(JmaK1pn_hvplc2P>zs*;9uEWaJT!6M+fwyCJlUshYS$wfxg16h> z=3RldLR*7ceX?7Avs{3+TYt1%f3;S4t>^0QS8I!0f3$mxq^q^XNMw**fVMwYdrxkj zMPG?PSA0ifk4$NoT79!bUWr6si9uR|SbDESUWZzKvr=-TLtKPJUWQ6%l}>G(Mq!IW zU5He5r$JhQO>CS$SAR)nlSN>PP;a3}VvR*#h*fv1PHv%Bdap=jkV$2cO>3DxR(?)v znm$*4Rd%XNYMMY=giC0bKUjiFW|K;1lT2!vSbMNmcdJBSibGzAKwE}YcdSQbkVayS zKUjZ3T!=nad_h`(L|~0lbEQIDg+g74R(GmQYn(%0j8bx=NMw&uaG^E8=gCQhzLLh`d2&DJkd+)us|2dM|oyPV| zCNs$s-@`rVH+%Q(?yaQImR<_=CR;;YwMqUFiOGf@^;J$OkR&mqZ}>;j)6L1bODppHkxWFtZrzqP2@dXxQ)_am(V7HzPrTM1w@MC_M6z7Vnv4{p6q>v zFeMg^&U}R9lQcz-mj588_c;M?VbWVLpB$ObUZMtfA~I1O`&EH$>B}1f$CwAn8mJ@P z&!H&hK%%qOeq4H)MGfGYgxi|rcc576eWV70i2Kg5Fn-{ZqUXt z<{Us^{NftFHvTw+z$%W>c8HX8;YgOs9{nDs(3spN`l#3aYSmjNS+8#WR+~hV-oxo> zZ&|4mEkL|v;{FmO{%%AUa&Eu@7WWN1NQ?yXiQs&o_W8v7)5kupoMCwm4cbKKYA-*q zYKEcHjq2c-{9RS*UQu_x1!$2`$u6WG)xD(=SYH2iIbz~_khlt(wk6I8$jQnO(@0K-v5)!nb=QCfEtiv;+1P%-2n*t`> z@{7p%ss7pd`!4Kwhd?238CC*rei!~b`HTO9-vSH(Wcg66!KHGn00000NkvXXu0mjf Dwx<;E literal 0 HcmV?d00001 diff --git a/templates/html/assets/images/search-background.png b/templates/html/assets/images/search-background.png new file mode 100644 index 0000000000000000000000000000000000000000..76295000a0361b4010af8ea71739bd62da70ea76 GIT binary patch literal 295 zcmV+?0oeYDP)I^@p_0n8MnOxY4cF?5x!5gRsf5+3&mI z_m8{Ow%zl9t;dzX*s9X$p2*+1;P#uw-HEl%v)b{e(C31%$b`*gkN^MxQAtEWRCwAX z!HE_^0002Njf9XpN1^!tfAsd5VHTG|^db8U}fi7AzZCsS=eY6E;iTp1V`^$d)GJPuAy9v)sT9X&aDMHUujYdc37Svhkn zTYE=WCs$8Zbxk%lb^$>lOB;Jrb1QXCZ6+orNhxV@2}xyTH9md;XLoM}MHLr!Zy{k3 zLt`^mR#saF7f~^B9X&%wXLn;$3odSMdxPs8K%0U*T^vIyZn+%1Ddq&^UhHg{VC%i` z$N%~-Cdc=(Ji62sq4P-9sjy?Wgo~zKlJMRaE?RO)LivFmZZ411^B!rJ1$HlA;i42a jv2*{4qZ(;*nRpn2%*52^9Plgy8pq)2>gTe~DWM4fif~Q} literal 0 HcmV?d00001 diff --git a/templates/html/assets/javascripts/tabs.js b/templates/html/assets/javascripts/tabs.js new file mode 100644 index 000000000..7c974209f --- /dev/null +++ b/templates/html/assets/javascripts/tabs.js @@ -0,0 +1,506 @@ +/** + * @author Ryan Johnson + * @copyright 2008 PersonalGrid Corporation + * @package LivePipe UI + * @license MIT + * @url http://livepipe.net/core + * @require prototype.js + */ + +if(typeof(Control) == 'undefined') + Control = {}; + +var $proc = function(proc){ + return typeof(proc) == 'function' ? proc : function(){return proc}; +}; + +var $value = function(value){ + return typeof(value) == 'function' ? value() : value; +}; + +Object.Event = { + extend: function(object){ + object._objectEventSetup = function(event_name){ + this._observers = this._observers || {}; + this._observers[event_name] = this._observers[event_name] || []; + }; + object.observe = function(event_name,observer){ + if(typeof(event_name) == 'string' && typeof(observer) != 'undefined'){ + this._objectEventSetup(event_name); + if(!this._observers[event_name].include(observer)) + this._observers[event_name].push(observer); + }else + for(var e in event_name) + this.observe(e,event_name[e]); + }; + object.stopObserving = function(event_name,observer){ + this._objectEventSetup(event_name); + if(event_name && observer) + this._observers[event_name] = this._observers[event_name].without(observer); + else if(event_name) + this._observers[event_name] = []; + else + this._observers = {}; + }; + object.observeOnce = function(event_name,outer_observer){ + var inner_observer = function(){ + outer_observer.apply(this,arguments); + this.stopObserving(event_name,inner_observer); + }.bind(this); + this._objectEventSetup(event_name); + this._observers[event_name].push(inner_observer); + }; + object.notify = function(event_name){ + this._objectEventSetup(event_name); + var collected_return_values = []; + var args = $A(arguments).slice(1); + try{ + for(var i = 0; i < this._observers[event_name].length; ++i) + collected_return_values.push(this._observers[event_name][i].apply(this._observers[event_name][i],args) || null); + }catch(e){ + if(e == $break) + return false; + else + throw e; + } + return collected_return_values; + }; + if(object.prototype){ + object.prototype._objectEventSetup = object._objectEventSetup; + object.prototype.observe = object.observe; + object.prototype.stopObserving = object.stopObserving; + object.prototype.observeOnce = object.observeOnce; + object.prototype.notify = function(event_name){ + if(object.notify){ + var args = $A(arguments).slice(1); + args.unshift(this); + args.unshift(event_name); + object.notify.apply(object,args); + } + this._objectEventSetup(event_name); + var args = $A(arguments).slice(1); + var collected_return_values = []; + try{ + if(this.options && this.options[event_name] && typeof(this.options[event_name]) == 'function') + collected_return_values.push(this.options[event_name].apply(this,args) || null); + for(var i = 0; i < this._observers[event_name].length; ++i) + collected_return_values.push(this._observers[event_name][i].apply(this._observers[event_name][i],args) || null); + }catch(e){ + if(e == $break) + return false; + else + throw e; + } + return collected_return_values; + }; + } + } +}; + +/* Begin Core Extensions */ + +//Element.observeOnce +Element.addMethods({ + observeOnce: function(element,event_name,outer_callback){ + var inner_callback = function(){ + outer_callback.apply(this,arguments); + Element.stopObserving(element,event_name,inner_callback); + }; + Element.observe(element,event_name,inner_callback); + } +}); + +//mouseenter, mouseleave +//from http://dev.rubyonrails.org/attachment/ticket/8354/event_mouseenter_106rc1.patch +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + //begin extension + if(!Prototype.Browser.IE){ + eventName = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + }[eventName] || eventName; + } + //end extension + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + //begin extension + if(!(Prototype.Browser.IE) && ['mouseenter','mouseleave'].include(eventName)){ + wrapper = wrapper.wrap(function(proceed,event) { + var rel = event.relatedTarget; + var cur = event.currentTarget; + if(rel && rel.nodeType == Node.TEXT_NODE) + rel = rel.parentNode; + if(rel && rel != cur && !rel.descendantOf(cur)) + return proceed(event); + }); + } + //end extension + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +//mouse:wheel +(function(){ + function wheel(event){ + var delta; + // normalize the delta + if(event.wheelDelta) // IE & Opera + delta = event.wheelDelta / 120; + else if (event.detail) // W3C + delta =- event.detail / 3; + if(!delta) + return; + var custom_event = Event.element(event).fire('mouse:wheel',{ + delta: delta + }); + if(custom_event.stopped){ + Event.stop(event); + return false; + } + } + document.observe('mousewheel',wheel); + document.observe('DOMMouseScroll',wheel); +})(); + +/* End Core Extensions */ + +//from PrototypeUI +var IframeShim = Class.create({ + initialize: function() { + this.element = new Element('iframe',{ + style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none', + src: 'javascript:void(0);', + frameborder: 0 + }); + $(document.body).insert(this.element); + }, + hide: function() { + this.element.hide(); + return this; + }, + show: function() { + this.element.show(); + return this; + }, + positionUnder: function(element) { + var element = $(element); + var offset = element.cumulativeOffset(); + var dimensions = element.getDimensions(); + this.element.setStyle({ + left: offset[0] + 'px', + top: offset[1] + 'px', + width: dimensions.width + 'px', + height: dimensions.height + 'px', + zIndex: element.getStyle('zIndex') - 1 + }).show(); + return this; + }, + setBounds: function(bounds) { + for(prop in bounds) + bounds[prop] += 'px'; + this.element.setStyle(bounds); + return this; + }, + destroy: function() { + if(this.element) + this.element.remove(); + return this; + } +}); + +/** + * @author Ryan Johnson + * @copyright 2008 PersonalGrid Corporation + * @package LivePipe UI + * @license MIT + * @url http://livepipe.net/control/tabs + * @require prototype.js, livepipe.js + */ + +/*global window, document, Prototype, $, $A, $H, $break, Class, Element, Event, Control */ + +if(typeof(Prototype) == "undefined") { + throw "Control.Tabs requires Prototype to be loaded."; } +if(typeof(Object.Event) == "undefined") { + throw "Control.Tabs requires Object.Event to be loaded."; } + +Control.Tabs = Class.create({ + initialize: function(tab_list_container,options){ + if(!$(tab_list_container)) { + throw "Control.Tabs could not find the element: " + tab_list_container; } + this.activeContainer = false; + this.activeLink = false; + this.containers = $H({}); + this.links = []; + Control.Tabs.instances.push(this); + this.options = { + beforeChange: Prototype.emptyFunction, + afterChange: Prototype.emptyFunction, + hover: false, + linkSelector: 'li a', + setClassOnContainer: false, + activeClassName: 'active', + defaultTab: 'first', + autoLinkExternal: true, + targetRegExp: /#(.+)$/, + showFunction: Element.show, + hideFunction: Element.hide + }; + Object.extend(this.options,options || {}); + (typeof(this.options.linkSelector == 'string') ? + $(tab_list_container).select(this.options.linkSelector) : + this.options.linkSelector($(tab_list_container)) + ).findAll(function(link){ + return (/^#/).exec((Prototype.Browser.WebKit ? decodeURIComponent(link.href) : link.href).replace(window.location.href.split('#')[0],'')); + }).each(function(link){ + this.addTab(link); + }.bind(this)); + this.containers.values().each(Element.hide); + if(this.options.defaultTab == 'first') { + this.setActiveTab(this.links.first()); + } else if(this.options.defaultTab == 'last') { + this.setActiveTab(this.links.last()); + } else { + this.setActiveTab(this.options.defaultTab); } + var targets = this.options.targetRegExp.exec(window.location); + if(targets && targets[1]){ + targets[1].split(',').each(function(target){ + this.setActiveTab(this.links.find(function(link){ + return link.key == target; + })); + }.bind(this)); + } + if(this.options.autoLinkExternal){ + $A(document.getElementsByTagName('a')).each(function(a){ + if(!this.links.include(a)){ + var clean_href = a.href.replace(window.location.href.split('#')[0],''); + if(clean_href.substring(0,1) == '#'){ + if(this.containers.keys().include(clean_href.substring(1))){ + $(a).observe('click',function(event,clean_href){ + this.setActiveTab(clean_href.substring(1)); + }.bindAsEventListener(this,clean_href)); + } + } + } + }.bind(this)); + } + }, + addTab: function(link){ + this.links.push(link); + link.key = link.getAttribute('href').replace(window.location.href.split('#')[0],'').split('#').last().replace(/#/,''); + var container = $(link.key); + if(!container) { + throw "Control.Tabs: #" + link.key + " was not found on the page."; } + this.containers.set(link.key,container); + link[this.options.hover ? 'onmouseover' : 'onclick'] = function(link){ + if(window.event) { + Event.stop(window.event); } + this.setActiveTab(link); + return false; + }.bind(this,link); + }, + setActiveTab: function(link){ + if(!link && typeof(link) == 'undefined') { + return; } + if(typeof(link) == 'string'){ + this.setActiveTab(this.links.find(function(_link){ + return _link.key == link; + })); + }else if(typeof(link) == 'number'){ + this.setActiveTab(this.links[link]); + }else{ + if(this.notify('beforeChange',this.activeContainer,this.containers.get(link.key)) === false) { + return; } + if(this.activeContainer) { + this.options.hideFunction(this.activeContainer); } + this.links.each(function(item){ + (this.options.setClassOnContainer ? $(item.parentNode) : item).removeClassName(this.options.activeClassName); + }.bind(this)); + (this.options.setClassOnContainer ? $(link.parentNode) : link).addClassName(this.options.activeClassName); + this.activeContainer = this.containers.get(link.key); + this.activeLink = link; + this.options.showFunction(this.containers.get(link.key)); + this.notify('afterChange',this.containers.get(link.key)); + } + }, + next: function(){ + this.links.each(function(link,i){ + if(this.activeLink == link && this.links[i + 1]){ + this.setActiveTab(this.links[i + 1]); + throw $break; + } + }.bind(this)); + }, + previous: function(){ + this.links.each(function(link,i){ + if(this.activeLink == link && this.links[i - 1]){ + this.setActiveTab(this.links[i - 1]); + throw $break; + } + }.bind(this)); + }, + first: function(){ + this.setActiveTab(this.links.first()); + }, + last: function(){ + this.setActiveTab(this.links.last()); + } +}); +Object.extend(Control.Tabs,{ + instances: [], + findByTabId: function(id){ + return Control.Tabs.instances.find(function(tab){ + return tab.links.find(function(link){ + return link.key == id; + }); + }); + } +}); +Object.Event.extend(Control.Tabs); From 647d93bcc46a69ae1dec7a19be0ef021f808a63a Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Tue, 22 Dec 2009 18:05:30 -0500 Subject: [PATCH 094/502] Use native `Array.isArray` when available; currently present in nightly webkit (up to 17x faster) and Opera 10.5 alpha (up to 5x faster). --- src/lang/object.js | 8 +++++++- vendor/pdoc | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 10c302387..e791d603a 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -217,7 +217,13 @@ function isArray(object) { return _toString.call(object) == "[object Array]"; } - + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } /** * Object.isHash(object) -> Boolean diff --git a/vendor/pdoc b/vendor/pdoc index e976b8441..147250bd6 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit e976b844144666113008a9159fa501f8f575bd1c +Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b From a7f05ee8b57f26713bdc72e8e095731d98e27f99 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 23 Dec 2009 05:52:51 +0100 Subject: [PATCH 095/502] doc: Fix a typo in Prototype.Selector.find which prevented proper documentation parsing. Add a default value for the index argument. --- src/dom/selector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index c3276035d..397f45fb2 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -42,10 +42,10 @@ window.$$ = function() { // Implementation provided by selector engine. /** - * Prototype.Selector.find(elements, expression[, index]) -> Element + * Prototype.Selector.find(elements, expression[, index = 0]) -> Element * - elements (Enumerable): a collection of DOM elements. * - expression (String): A CSS selector. - # - index: Numeric index of the match to return, or 0 if omitted. + * - index: Numeric index of the match to return, defaults to 0. * * Filters the given collection of elements with `expression` and returns the * first matching element (or the `index`th matching element if `index` is From fb93b80b59cf2c3d8e828741d1b73bdf23c36233 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 23 Dec 2009 06:32:59 +0100 Subject: [PATCH 096/502] doc: using pygments for syntax highlighting. --- Rakefile | 3 +- .../html/assets/stylesheets/pygments.css | 195 ++++++++++++++++++ templates/html/layout.erb | 4 +- vendor/pdoc | 2 +- 4 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 templates/html/assets/stylesheets/pygments.css diff --git a/Rakefile b/Rakefile index b6b3a370b..b4b03d8ad 100755 --- a/Rakefile +++ b/Rakefile @@ -75,7 +75,8 @@ module PrototypeHelper PDoc::Runner.new(temp_path, { :output => DOC_DIR, :templates => File.join(TEMPLATES_DIR, "html"), - :index_page => 'README.markdown' + :index_page => 'README.markdown', + :syntax_highlighter => :pygments }).run rm_rf temp_path diff --git a/templates/html/assets/stylesheets/pygments.css b/templates/html/assets/stylesheets/pygments.css new file mode 100644 index 000000000..08577379d --- /dev/null +++ b/templates/html/assets/stylesheets/pygments.css @@ -0,0 +1,195 @@ +.highlight { + background: #fff; +} +.highlight .c { + color: #998; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .k { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .cm { + color: #998; + font-style: italic; +} +.highlight .cp { + color: #999; + font-weight: bold; +} +.highlight .c1 { + color: #998; + font-style: italic; +} +.highlight .cs { + color: #999; + font-weight: bold; + font-style: italic; +} +.highlight .gd { + color: #000; + background-color: #fdd; +} +.highlight .gd .x { + color: #000; + background-color: #faa; +} +.highlight .ge { + font-style: italic; +} +.highlight .gr { + color: #a00; +} +.highlight .gh { + color: #999; +} +.highlight .gi { + color: #000; + background-color: #dfd; +} +.highlight .gi .x { + color: #000; + background-color: #afa; +} +.highlight .go { + color: #888; +} +.highlight .gp { + color: #555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaa; +} +.highlight .gt { + color: #a00; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #458; + font-weight: bold; +} +.highlight .m { + color: #099; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #458; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #900; + font-weight: bold; +} +.highlight .nf { + color: #900; + font-weight: bold; +} +.highlight .nn { + color: #555; +} +.highlight .nt { + color: #000080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .w { + color: #bbb; +} +.highlight .mf { + color: #099; +} +.highlight .mh { + color: #099; +} +.highlight .mi { + color: #099; +} +.highlight .mo { + color: #099; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .bp { + color: #999; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .il { + color: #099; +} diff --git a/templates/html/layout.erb b/templates/html/layout.erb index 6e6aed82a..862438980 100644 --- a/templates/html/layout.erb +++ b/templates/html/layout.erb @@ -7,10 +7,10 @@ <%= javascript_include_tag "prototype" %> - <%= javascript_include_tag "application", "code_highlighter", "tabs" %> + <%= javascript_include_tag "application", "tabs" %> <%= javascript_include_tag "item_index" %> - <%= stylesheet_link_tag "api" %> + <%= stylesheet_link_tag "api", "pygments" %> - - - - - - -
    - -
    - - <%= @content_for_layout %> - -
    - - - -
    - - diff --git a/templates/html/namespace.erb b/templates/html/namespace.erb deleted file mode 100644 index 5e5725637..000000000 --- a/templates/html/namespace.erb +++ /dev/null @@ -1,171 +0,0 @@ -<% d = @doc_instance %> - -<% @title = "#{d.full_name} #{d.type}" %> - -<%= include "partials/breadcrumbs", :object => d %> - -

    - <%= d.type %> <%= d.full_name %> -

    - -<% # Does it have a description? %> - -<% if d.description && !d.description.empty? %> -
    - -
    -

    Description

    -
    - -
    - <%= htmlize(d.description) %> -
    - -
    -<% end %> - -<% # Is it a CLASS? %> -<% if d.is_a?(Documentation::Klass) %> - - <% if d.superklass %> -
    -
    -

    Superclass

    -
    - -
    -

    <%= auto_link_code(d.superklass, false) %>

    -
    -
    - <% end %> - - <% unless d.subklasses.empty? %> -
    -
    -

    Subclasses

    -
    - -
    -

    <%= d.subklasses.map { |s| auto_link_code(s, false) }.join(', ') %>

    -
    -
    - <% end %> - -<% end %> - -<% # Does it have MIXINS? %> -<% unless d.mixins.compact.empty? %> -
    -
    -

    Includes

    -
    - -
    -

    <%= d.mixins.map { |m| auto_link_code(m, false) }.join(', ') %>

    -
    -
    -<% end %> - -<% # Are there related utilities? %> -<% unless d.related_utilities.empty? %> -
    -
    -

    Related utilities

    -
    - -
    -

    <%= d.related_utilities.map { |u| auto_link_code(u, false) }.join(', ') %>

    -
    -
    -<% end %> - -<% # List its methods. %> -<% unless d.all_methods.empty? && d.mixins.empty? %> -
    -
    -

    Methods

    -
    - -
    -
      - <% d.all_methods.each do |method| %> -
    • <%= auto_link_code(method, true, :class => class_names_for(method)) %>
    • - <% end %> -
    - - <% unless d.mixins.compact.empty? %> - <% d.mixins.each do |mixin| %> -

    Inherited from <%= auto_link(mixin) %>

    -
      - <% mixin.all_methods.each do |method| %> -
    • <%= auto_link_code(method, true, :class => class_names_for(method)) %>
    • - <% end %> -
    - <% end %> - <% end %> - - -
    - -
    -<% end %> - -<% if d.is_a?(Documentation::Klass) && d.constructor %> -
    -
    -

    Constructor

    -
    - -
    -
    <%= d.constructor.ebnf_expressions.join("\n").strip %>
    - <% unless d.constructor.arguments.empty? %> -
      - <% d.constructor.arguments.each do |arg| %> -
    • - <%= arg.name %> - <% unless arg.types.empty? %> - (<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>) - <% end %> - <%= ' – ' + inline_htmlize(arg.description) unless arg.description.empty? %> -
    • - <% end %> -
    - <% end %> - - -

    <%= htmlize(d.constructor.description) %>

    - -
    - -
    -<% end %> - - -<% -types = { - :constants => "Constants", - :klass_methods => "Class methods", - :klass_properties => "Class properties", - :instance_methods => "Instance methods", - :instance_properties => "Instance properties" -} -%> - -<% types.each do |method, title| %> - <% methods = d.send(method) %> - <% unless methods.empty? %> -
    - -
    -

    <%= title %>

    -
    - -
    -
      - <%= include "partials/short_description", :collection => methods %> -
    - -
    -
    - <% end %> -<% end %> \ No newline at end of file diff --git a/templates/html/partials/breadcrumbs.erb b/templates/html/partials/breadcrumbs.erb deleted file mode 100644 index 7e02719d4..000000000 --- a/templates/html/partials/breadcrumbs.erb +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/templates/html/partials/short_description.erb b/templates/html/partials/short_description.erb deleted file mode 100644 index c4ad7848d..000000000 --- a/templates/html/partials/short_description.erb +++ /dev/null @@ -1,48 +0,0 @@ -
  • -

    <%= object.name %> - #

    - - <% if object.signature %> - <%= method_synopsis(object) %> - <% end %> - - <% if object.is_a?(Documentation::Method) %> - <% unless object.arguments.empty? %> -
      - <% object.arguments.each do |arg| %> -
    • - <%= arg.name %> - <% unless arg.types.empty? %> - (<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>) - <% end %> - <%= ' – ' + inline_htmlize(arg.description) unless arg.description.empty? %> -
    • - <% end %> -
    - <% end %> - <% end %> - - <%= htmlize(object.description) %> - - <% # Does it have an alias? %> - <% unless object.aliases.empty? %> -

    Aliased as: <%= object.aliases.map { |a| auto_link_code(a, false) }.join(', ') %>

    - <% end %> - - <% # Is it an alias of something else? %> - <% if object.alias? %> -

    Alias of: <%= auto_link_code(object.alias_of, false) %>

    - <% end %> - - <% # Is it related to something? %> - <% if object.is_a?(Documentation::Utility) && object.related_to %> - - <% end %> - - <% # Is it methodized? %> - <% if object.is_a?(Documentation::Method) && object.methodized? %> -

    This method can be called either as an instance method or as a generic method. If calling as a generic, pass the instance in as the first argument.

    - <% end %> - -
  • diff --git a/templates/html/section.erb b/templates/html/section.erb deleted file mode 100644 index f433573bd..000000000 --- a/templates/html/section.erb +++ /dev/null @@ -1,99 +0,0 @@ -<% @title = "#{@doc_instance.full_name}" %> -<% section = @doc_instance %> - -<%= include "partials/breadcrumbs", :object => section %> - -

    - Section <%= section.name %> -

    - -
    - -
    -

    Description

    -
    - -
    - <%= htmlize(section.description) %> -
    - -
    - -<% # Iterate over the items in this section. %> - -<% utilities = section.utilities %> -<% unless utilities.empty? %> - -
    -
    -

    Utilities

    -
    - -
    -
      - <% utilities.each do |utility| %> -
    • <%= auto_link_code(utility) %>
    • - <% end %> -
    -
    -
    - -<% end %> - -<% namespaces = section.namespaces %> -<% unless namespaces.empty? %> - -
    -
    -

    Namespaces

    -
    - -
    -
      - <% namespaces.each do |namespace| %> -
    • -

      - <%= namespace.full_name %> -

      - - <% unless namespace.short_description.nil? %> -

      <%= htmlize(namespace.short_description) %>

      - <% end %> - -
    • - <% end %> -
    -
    -
    - -<% end %> - -<% klasses = section.klasses %> -<% unless klasses.empty? %> - -
    - -
    -

    Classes

    -
    - -
    -
      - <% klasses.each do |klass| %> -
    • -

      - <%= klass.full_name %> -

      - - <% unless klass.short_description.nil? %> -

      <%= htmlize(klass.short_description) %>

      - <% end %> - -
    • - <% end %> -
    -
    - -
    - -<% end %> \ No newline at end of file diff --git a/templates/html/utility.erb b/templates/html/utility.erb deleted file mode 100644 index 3ffc98d24..000000000 --- a/templates/html/utility.erb +++ /dev/null @@ -1,21 +0,0 @@ -<% d = @doc_instance %> - -<% @title = "#{@doc_instance.full_name} utility" %> - -<%= include "partials/breadcrumbs", :object => d %> - -

    - utility <%= @doc_instance.name %> -

    - -
    -
    -

    Description

    -
    - -
    -
      - <%= include "partials/short_description", :collection => [@doc_instance] %> -
    -
    -
    \ No newline at end of file diff --git a/vendor/pdoc b/vendor/pdoc index 525737c9e..e4290279a 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 525737c9e8696210463edfb4a855e01f39fb25ec +Subproject commit e4290279a1d42bc02b09b234291a2d05c0134a43 From 56fb5b84a1dab52000b4440d4429c74ad2f074d5 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 23 Dec 2009 01:54:19 -0500 Subject: [PATCH 098/502] No need to use expensive try/catch when check for element being orphaned suffices. --- src/dom/dom.js | 26 +++++++++----------------- test/unit/dom_test.js | 4 ++++ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 6ab02419b..7f0c05404 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1190,11 +1190,13 @@ Element.Methods = { **/ cumulativeOffset: function(element) { var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } return Element._returnOffset(valueL, valueT); }, @@ -1558,8 +1560,7 @@ else if (Prototype.Browser.IE) { function(proceed, element) { element = $(element); // IE throws an error if element is not in document - try { element.offsetParent } - catch(e) { return $(document.body) } + if (!element.parentNode) return $(document.body); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); @@ -1573,8 +1574,7 @@ else if (Prototype.Browser.IE) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } + if (!element.parentNode) return Element._returnOffset(0, 0); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); // Trigger hasLayout on the offset parent so that IE6 reports @@ -1590,14 +1590,6 @@ else if (Prototype.Browser.IE) { ); }); - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 32995845c..deb20261e 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1322,6 +1322,10 @@ new Test.Unit.Runner({ this.assertEnumEqual([0,0], offset); this.assertIdentical(0, offset.top); this.assertIdentical(0, offset.left); + + var innerEl = new Element('div'), outerEl = new Element('div'); + outerEl.appendChild(innerEl); + this.assertEnumEqual([0,0], innerEl.cumulativeOffset()); }, testViewportOffset: function() { From 2e3e02d92c06803bfdedf0ba73dc53d42efc5880 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 24 Dec 2009 03:19:14 +0100 Subject: [PATCH 099/502] doc: Fix documentation errors in Ajax.Response. --- src/ajax/response.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ajax/response.js b/src/ajax/response.js index 658df87e1..18cd8ba80 100644 --- a/src/ajax/response.js +++ b/src/ajax/response.js @@ -121,7 +121,7 @@ Ajax.Response = Class.create({ }, /** - * Ajax.Response.getResponseHeader(name) -> String + * Ajax.Response#getResponseHeader(name) -> String * * Returns the value of the requested header if present; throws an error * otherwise. This is just a wrapper around the `XmlHttpRequest` method of @@ -132,7 +132,7 @@ Ajax.Response = Class.create({ }, /** - * Ajax.Response.getAllResponseHeaders() -> String + * Ajax.Response#getAllResponseHeaders() -> String * * Returns a string containing all headers separated by line breaks; throws * an error if no headers exist. This is just a wrapper around the From 8a8af0a729487a7f69874ea00074e3e931e8b215 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 24 Dec 2009 07:32:32 +0100 Subject: [PATCH 100/502] doc: Fix documentation for Template. --- src/lang/template.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/template.js b/src/lang/template.js index 8b396a89d..fce8476e5 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -82,14 +82,14 @@ * inadequate, there's a provision for customization. `Template`'s * constructor accepts an optional second argument that is a regular expression * object to match the replaceable symbols in the template string. Let's put - * together a template that uses a syntax similar to the ubiquitous `<&= %>` + * together a template that uses a syntax similar to the ubiquitous `<%= %>` * constructs: * - * // matches symbols like '<&= field %>' + * // matches symbols like '<%= field %>' * var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/; * * var t = new Template( - * '
    Name: <&= name %>, Age: <&=age%>
    ', + * '
    Name: <%= name %>, Age: <%= age %>
    ', * syntax); * t.evaluate( {name: 'John Smith', age: 26} ); * // ->
    Name: John Smith, Age: 26
    From 4cc67f2e8e2a47b0c4e681ce9cda0374e6647997 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Tue, 29 Dec 2009 01:53:16 +0100 Subject: [PATCH 101/502] doc: Minor cleanup. --- src/dom/dom.js | 6 +++--- src/dom/event.js | 3 ++- src/dom/form.js | 16 ++++++++-------- src/dom/selector.js | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 7f0c05404..e0daa1d85 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1,6 +1,6 @@ /** section: DOM, related to: Element * $(id) -> Element - * $(id...) -> [Element]... + * $(id...) -> [Element...] * - id (String | Element): A DOM node or a string that references a node's * ID. * @@ -2281,8 +2281,8 @@ Element.addMethods = function(methods) { Element.cache = { }; }; -/** section: DOM - * document.viewport +/** + * document.viewport * * The `document.viewport` namespace contains methods that return information * about the viewport — the rectangle that represents the portion of a web diff --git a/src/dom/event.js b/src/dom/event.js index d52c1da9b..3c28a8149 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -621,6 +621,7 @@ * ...and then to remove: * * $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right + * **/ function stopObserving(element, eventName, handler) { element = $(element); @@ -744,7 +745,7 @@ }); /** section: DOM - * document + * document * * Prototype extends the built-in `document` object with several convenience * methods related to events. diff --git a/src/dom/form.js b/src/dom/form.js index 8b8d9c121..1dc1deb78 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -248,7 +248,7 @@ Form.Methods = { /*--------------------------------------------------------------------------*/ -/** section: DOM +/** * Form.Element * * Utilities for dealing with form controls in the DOM. @@ -359,7 +359,7 @@ Form.Element.Methods = { }, /** - * Form.Element#activate(element) -> Element + * Form.Element.activate(@element) -> Element * * Gives focus to a form control and selects its contents if it is a text * input. @@ -479,7 +479,7 @@ Form.Element.Serializers = { * Abstract **/ -/** section: DOM +/** * class Abstract.TimedObserver * * An abstract DOM element observer class, subclasses of which can be used to periodically @@ -528,7 +528,7 @@ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { } }); -/** section: DOM +/** * class Form.Element.Observer < Abstract.TimedObserver * * An [[Abstract.TimedObserver]] subclass that watches for changes to a form field's value. @@ -551,7 +551,7 @@ Form.Element.Observer = Class.create(Abstract.TimedObserver, { } }); -/** section: DOM +/** * class Form.Observer < Abstract.TimedObserver * * An [[Abstract.TimedObserver]] subclass that watches for changes to a form. @@ -577,7 +577,7 @@ Form.Observer = Class.create(Abstract.TimedObserver, { /*--------------------------------------------------------------------------*/ -/** section: DOM +/** * class Abstract.EventObserver **/ Abstract.EventObserver = Class.create({ @@ -619,7 +619,7 @@ Abstract.EventObserver = Class.create({ } }); -/** section: DOM +/** * class Form.Element.EventObserver < Abstract.EventObserver **/ Form.Element.EventObserver = Class.create(Abstract.EventObserver, { @@ -628,7 +628,7 @@ Form.Element.EventObserver = Class.create(Abstract.EventObserver, { } }); -/** section: DOM +/** * class Form.EventObserver < Abstract.EventObserver **/ Form.EventObserver = Class.create(Abstract.EventObserver, { diff --git a/src/dom/selector.js b/src/dom/selector.js index 397f45fb2..0804337e9 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,4 +1,4 @@ -/** related to: Prototype.Selector +/** section: DOM, related to: Prototype.Selector * $$(expression...) -> [Element...] * * Returns all elements in the document that match the provided CSS selectors. From 6c1790ac4e6a857742e8fdc761e6305a14c79a17 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Tue, 29 Dec 2009 01:54:56 +0100 Subject: [PATCH 102/502] doc: Fix Template doc to avoid issues with Sprockets. --- src/lang/template.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lang/template.js b/src/lang/template.js index fce8476e5..f9773f566 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -82,14 +82,14 @@ * inadequate, there's a provision for customization. `Template`'s * constructor accepts an optional second argument that is a regular expression * object to match the replaceable symbols in the template string. Let's put - * together a template that uses a syntax similar to the ubiquitous `<%= %>` + * together a template that uses a syntax similar to the now ubiquitous `{{ }}` * constructs: * - * // matches symbols like '<%= field %>' - * var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/; + * // matches symbols like '{{ field }}' + * var syntax = /(^|.|\r|\n)(\{{\s*(\w+)\s*}})/; * * var t = new Template( - * '
    Name: <%= name %>, Age: <%= age %>
    ', + * '
    Name: {{ name }}, Age: {{ age }}
    ', * syntax); * t.evaluate( {name: 'John Smith', age: 26} ); * // ->
    Name: John Smith, Age: 26
    From a260913a303df5ec8e92a439a90fd364969a4a31 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 30 Dec 2009 01:33:37 -0500 Subject: [PATCH 103/502] Remove SETATTRIBUTE_IGNORES_NAME feature test, replacing it with a simpler HAS_EXTENDED_CREATE_ELEMENT_SYNTAX one. This avoids invalid injection of FORM into a root element (HTML). --- src/dom/dom.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 7f0c05404..2620f30db 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -110,31 +110,26 @@ if (!Node.ELEMENT_NODE) { * // The new way: * var a = new Element('a', {'class': 'foo', href: '/foo.html'}).update("Next page"); **/ -(function(global) { - // setAttribute is broken in IE (particularly when setting name attribute) - // see: http://msdn.microsoft.com/en-us/library/ms536389.aspx - var SETATTRIBUTE_IGNORES_NAME = (function(){ - var elForm = document.createElement("form"), - elInput = document.createElement("input"), - root = document.documentElement; - elInput.setAttribute("name", "test"); - elForm.appendChild(elInput); - root.appendChild(elForm); - var isBuggy = elForm.elements - ? (typeof elForm.elements.test == "undefined") - : null; - root.removeChild(elForm); - elForm = elInput = null; - return isBuggy; +(function(global) { + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } })(); var element = global.Element; + global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (SETATTRIBUTE_IGNORES_NAME && attributes.name) { + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); @@ -142,12 +137,14 @@ if (!Node.ELEMENT_NODE) { if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; + Object.extend(global.Element, element || { }); if (element) global.Element.prototype = element.prototype; + })(this); -Element.cache = { }; Element.idCounter = 1; +Element.cache = { }; Element.Methods = { /** From 475f1797e13c21891e112f468720d5b03409fc4b Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Wed, 30 Dec 2009 01:45:59 -0500 Subject: [PATCH 104/502] No need to check for `window.Node`, since `var Node` already declares it if missing. --- src/dom/dom.js | 2 +- vendor/pdoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3756607d1..86f3b771b 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -38,7 +38,7 @@ if (Prototype.BrowserFeatures.XPath) { /*--------------------------------------------------------------------------*/ -if (!window.Node) var Node = { }; +if (!Node) var Node = { }; if (!Node.ELEMENT_NODE) { // DOM level 2 ECMAScript Language Binding diff --git a/vendor/pdoc b/vendor/pdoc index e4290279a..147250bd6 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit e4290279a1d42bc02b09b234291a2d05c0134a43 +Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b From 6839886699bea0e51dd6acbe459154bbc2fe8d27 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 31 Dec 2009 19:17:34 +0100 Subject: [PATCH 105/502] doc: Update PDoc. Default to BlueCloth Markdown parser to avoid Maruku warning. --- Rakefile | 10 ++++++---- vendor/pdoc | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 9de326370..2b4e2ebaf 100755 --- a/Rakefile +++ b/Rakefile @@ -72,11 +72,13 @@ module PrototypeHelper ) rm_rf DOC_DIR - PDoc::Runner.new(temp_path, { - :output => DOC_DIR, + PDoc.run({ + :source_files => [temp_path], + :destination => DOC_DIR, :index_page => 'README.markdown', - :syntax_highlighter => :pygments - }).run + :syntax_highlighter => :pygments, + :markdown_parser => :bluecloth + }) rm_rf temp_path end diff --git a/vendor/pdoc b/vendor/pdoc index 147250bd6..472a55dd0 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b +Subproject commit 472a55dd0019acf034d4f72522915a5e9efd0a1a From f12b83ef236a54306c7f686ae4c1c45910e2fc57 Mon Sep 17 00:00:00 2001 From: Dan Dean Date: Thu, 7 Jan 2010 05:07:02 +0100 Subject: [PATCH 106/502] doc: Merged/updated old docs for Try.these [#114 state:fixed_in_branch] --- src/lang.js | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/lang.js b/src/lang.js index a619f7f02..3dd8e7de6 100644 --- a/src/lang.js +++ b/src/lang.js @@ -10,12 +10,44 @@ var Abstract = { }; * Try **/ -/** +/** deprecated * Try.these(function...) -> ? * - function (Function): A function that may throw an exception. * * Accepts an arbitrary number of functions and returns the result of the * first one that doesn't throw an error. + * + * **This method is deprecated.** + * + *
    More information
    + * + * `Try.these` provides a simple idiom for trying out blocks of code in + * sequence. Such a sequence of attempts usually represents a downgrading + * approach to obtaining a given feature. + * + * In this example from Prototype's [[Ajax]] internals, we want to get an + * `XMLHttpRequest` object. Internet Explorer 6 and earlier, however, does not + * provide it as a vanilla JavaScript object, and will throw an error if we + * attempt a simple instantiation. Also, over time, its proprietary way + * evolved, changing COM interface names. + * + * `Try.these` will try several ways in sequence, from the best (and, + * theoretically, most widespread) one to the oldest and rarest way, returning + * the result of the first successful function. + * + * If none of the blocks succeeded, `Try.these` will return `undefined`, which + * will cause the `Ajax.getTransport` method in the example below to return + * `false`, provided as a fallback result value. + * + * var Ajax = { + * getTransport: function() { + * return Try.these( + * function() { return new XMLHttpRequest() }, + * function() { return new ActiveXObject('Msxml2.XMLHTTP') }, + * function() { return new ActiveXObject('Microsoft.XMLHTTP') } + * ) || false; + * } + * }; **/ var Try = { these: function() { From 0cfdd9b360d043a21228cd1a0d3f146a0617c7e1 Mon Sep 17 00:00:00 2001 From: dandean Date: Wed, 6 Jan 2010 23:05:27 -0800 Subject: [PATCH 107/502] doc: Merged/updated old docs for Element.Methods [#73 state:fixed_in_branch] --- src/dom/dom.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 86f3b771b..c363eeb95 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -146,6 +146,24 @@ if (!Node.ELEMENT_NODE) { Element.idCounter = 1; Element.cache = { }; +/** + * mixin Element.Methods + * + * `Element.Methods` is a mixin for DOM elements. The methods of this object + * are accessed through the [[$]] utility or through the [[Element]] object and + * shouldn't be accessed directly. + * + * ##### Examples + * + * Hide the element + * + * $(element).hide(); + * + * Return an [[Enumerable]] of all descendant nodes of the element with the id + * "article" + * + * $('articles').descendants(); +**/ Element.Methods = { /** * Element.visible(@element) -> boolean From b06a5358a6071caddd98e6ac0838cddef0e056bd Mon Sep 17 00:00:00 2001 From: Bertrand Chardon Date: Sat, 21 Nov 2009 01:15:45 +0100 Subject: [PATCH 108/502] doc: Merged/updated old docs for lang utility methods [#135 state:fixed_in_branch] --- src/lang/array.js | 91 ++++++++++++++++++++++++++++++++++++++++------- src/lang/hash.js | 25 +++++++++---- src/lang/range.js | 42 +++++++++++++++++++--- 3 files changed, 135 insertions(+), 23 deletions(-) diff --git a/src/lang/array.js b/src/lang/array.js index 389a1228d..52984db56 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -1,11 +1,59 @@ /** section: Language, related to: Array - * $A(iterable) -> Array - * - * Accepts an array-like collection (anything with numeric indices) and returns - * its equivalent as an actual Array object. - * This method is a convenience alias of [[Array.from]], but is the preferred way - * of casting to an Array. - **/ + * $A(iterable) -> actualArray + * + * Accepts an array-like collection (anything with numeric indices) and returns its equivalent + * as an actual `Array` object. This method is a convenience alias of [`Array.from`](/api/array/from), + * but is the preferred way of casting to an `Array`. + * + * The primary use of `$A()` is to obtain an actual `Array` object based on anything + * that could pass as an array (e.g. the `NodeList` or `HTMLCollection` objects returned + * by numerous DOM methods, or the predefined `arguments` reference within your functions). + * + * The reason you would want an actual `Array` is simple: [Prototype extends `Array`](/api/array) + * to equip it with numerous extra methods, and also mixes in the [`Enumerable`](/api/enumerable) + * module, which brings in another boatload of nifty methods. Therefore, in Prototype, + * actual `Array`s trump any other collection type you might otherwise get. + * + * The conversion performed is rather simple: `null`, `undefined` and `false` become + * an empty array; any object featuring an explicit `toArray` method (as many Prototype + * objects do) has it invoked; otherwise, we assume the argument "looks like an array" + * (e.g. features a `length` property and the `[]` operator), and iterate over its components + * in the usual way. + * + * When passed an array, `$A` _makes a copy_ of that array and returns it. + * + * ##### Examples + * + * The well-known DOM method [`document.getElementsByTagName()`](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-A6C9094) + * doesn't return an `Array`, but a `NodeList` object that implements the basic array + * "interface." Internet Explorer does not allow us to extend `Enumerable` onto `NodeList.prototype`, + * so instead we cast the returned `NodeList` to an `Array`: + * + * var paras = $A(document.getElementsByTagName('p')); + * paras.each(Element.hide); + * $(paras.last()).show(); + * + * Notice we had to use `each` and `Element.hide` because `$A` doesn't perform DOM extensions, + * since the array could contain anything (not just DOM elements). To use the `hide` + * instance method we first must make sure all the target elements are extended: + * + * $A(document.getElementsByTagName('p')).map(Element.extend).invoke('hide'); + * + * Want to display your arguments easily? `Array` features a `join` method, but the `arguments` + * value that exists in all functions *does not* inherit from `Array`. So, the tough + * way, or the easy way? + * + * // The hard way... + * function showArgs() { + * alert(Array.prototype.join.call(arguments, ', ')); + * } + * + * // The easy way... + * function showArgs() { + * alert($A(arguments).join(', ')); + * } +**/ + function $A(iterable) { if (!iterable) return []; // Safari <2.0.4 crashes when accessing property of a node list with property accessor. @@ -17,13 +65,30 @@ function $A(iterable) { } /** section: Language, related to: Array - * $w(string) -> Array - * - string (String): A string with zero or more spaces. - * - * Splits a string into an array, treating all whitespace as delimiters. - * - * Equivalent to Ruby's `%w{foo bar}` or Perl's `qw(foo bar)`. + * $w(String) -> Array + * + * Splits a string into an `Array`, treating all whitespace as delimiters. Equivalent + * to Ruby's `%w{foo bar}` or Perl's `qw(foo bar)`. + * + * This is one of those life-savers for people who just hate commas in literal arrays :-) + * + * ### Examples + * + * $w('apples bananas kiwis') + * // -> ['apples', 'bananas', 'kiwis'] + * + * This can slightly shorten code when writing simple iterations: + * + * $w('apples bananas kiwis').each(function(fruit){ + * var message = 'I like ' + fruit + * // do something with the message + * }) + * + * This also becomes sweet when combined with [`Element`](/api/element) functions: + * + * $w('ads navbar funkyLinks').each(Element.hide); **/ + function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); diff --git a/src/lang/hash.js b/src/lang/hash.js index 3ff86c04c..5ea34e276 100644 --- a/src/lang/hash.js +++ b/src/lang/hash.js @@ -1,10 +1,23 @@ /** section: Language, related to: Hash - * $H([object]) -> Hash - * - * Creates a `Hash`. This is purely a convenience wrapper around the Hash - * constructor, it does not do anything other than pass any argument it's - * given into the Hash constructor and return the result. - **/ + * $H([obj]) -> Hash + * + * Creates a [`Hash`](/api/hash) (which is synonymous to "map" or "associative array" + * for our purposes). A convenience wrapper around the `Hash` constructor, with a safeguard + * that lets you pass an existing `Hash` object and get it back untouched (instead of + * uselessly cloning it). + * + * The `$H` function is the shorter way to obtain a hash (prior to 1.5 final, it was + * the *only* proper way of getting one). + * + * ##### Example + * + * var h = $H({name: 'John', age: 26, country: 'Australia'}); + * // Equivalent to: + * var h = new Hash({name: 'John', age: 26, country: 'Australia'}); + * // Can then be accessed the classic Hash way + * h.get('country'); + * // -> 'Australia' +**/ function $H(object) { return new Hash(object); }; diff --git a/src/lang/range.js b/src/lang/range.js index 967cde499..6b209b268 100644 --- a/src/lang/range.js +++ b/src/lang/range.js @@ -22,11 +22,45 @@ /** section: Language * $R(start, end[, exclusive = false]) -> ObjectRange - * - * Creates a new ObjectRange object. - * This method is a convenience wrapper around the [[ObjectRange]] constructor, - * but $R is the preferred alias. + * + * Creates a new `ObjectRange` object. This method is a convenience wrapper around the + * [`ObjectRange`](/api/objectRange) constructor, but `$R` is the preferred alias. + * + * [`ObjectRange`](/api/objectRange) instances represent a range of consecutive values, + * be they numerical, textual, or of another type that semantically supports value ranges. + * See the type's documentation for further details, and to discover how your own objects + * can support value ranges. + * + * The `$R` function takes exactly the same arguments as the original constructor: the + * **lower and upper bounds** (value of the same, proper type), and **whether the upper + * bound is exclusive** or not. By default, the upper bound is inclusive. + * + * ##### Examples + * + * $R(0, 10).include(10) + * // -> true + * + * $A($R(0, 5)).join(', ') + * // -> '0, 1, 2, 3, 4, 5' + * + * $A($R('aa', 'ah')).join(', ') + * // -> 'aa, ab, ac, ad, ae, af, ag, ah' + * + * $R(0, 10, true).include(10) + * // -> false + * + * $R(0, 10, true).each(function(value) { + * // invoked 10 times for value = 0 to 9 + * }); + * + * Note that `ObjectRange` mixes in the [`Enumerable`](/api/enumerable) module: this + * makes it easy to convert a range to an `Array` (`Enumerable` provides the [`toArray`](/api/enumerable/toArray) + * method, which makes the [`$A`](dollar-a) conversion straightforward), or to iterate + * through values. (Note, however, that getting the bounds back will be more efficiently + * done using the `start` and `end` properties than calling the [`min()`](/api/enumerable/min) + * and [`max()`](/api/enumerable/max) methods). **/ + function $R(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } From e7e484265b588174793118d27926466a7756751a Mon Sep 17 00:00:00 2001 From: dandean Date: Fri, 8 Jan 2010 17:10:36 -0800 Subject: [PATCH 109/502] doc: Merge/update the old docs for document object into source [#91 state:fixed_in_branch] --- src/dom/event.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dom/event.js b/src/dom/event.js index 3c28a8149..944b3b44a 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -753,7 +753,15 @@ Object.extend(document, { /** * document.fire(eventName[, memo[, bubble = true]]) -> Event - * See [[Event.fire]]. + * - memo (?): Metadata for the event. Will be accessible through the + * event's `memo` property. + * - bubble (Boolean): Whether the event will bubble. + * + * Fires a custom event of name `eventName` with `document` as the target. + * + * `document.fire` is the document-wide version of [[Element.fire]]. + * + * Custom events must include a colon (`:`) in their names. **/ fire: fire.methodize(), From 717590aaff4220ee0ce63b988f6f7f200c4466c3 Mon Sep 17 00:00:00 2001 From: dandean Date: Fri, 8 Jan 2010 12:22:53 -0800 Subject: [PATCH 110/502] doc: Merge/update old docs for document.viewport into source [#92 state:fixed_in_branch] --- src/dom/dom.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index c363eeb95..17d48c310 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2309,9 +2309,16 @@ document.viewport = { /** * document.viewport.getDimensions() -> Object * - * Returns the size of the viewport. + * Returns an object containing viewport dimensions in the form + * `{ width: Number, height: Number }`. * - * Returns an object of the form `{ width: Number, height: Number }`. + * The _viewport_ is the subset of the browser window that a page occupies + * — the "usable" space in a browser window. + * + * ##### Example + * + * document.viewport.getDimensions(); + * //-> { width: 776, height: 580 } **/ getDimensions: function() { return { width: this.getWidth(), height: this.getHeight() }; @@ -2324,6 +2331,15 @@ document.viewport = { * * Returns an array in the form of `[leftValue, topValue]`. Also accessible * as properties: `{ left: leftValue, top: topValue }`. + * + * ##### Examples + * + * document.viewport.getScrollOffsets(); + * //-> { left: 0, top: 0 } + * + * window.scrollTo(0, 120); + * document.viewport.getScrollOffsets(); + * //-> { left: 0, top: 120 } **/ getScrollOffsets: function() { return Element._returnOffset( @@ -2360,6 +2376,8 @@ document.viewport = { * document.viewport.getWidth() -> Number * * Returns the width of the viewport. + * + * Equivalent to calling `document.viewport.getDimensions().width`. **/ viewport.getWidth = define.curry('Width'); @@ -2367,6 +2385,8 @@ document.viewport = { * document.viewport.getHeight() -> Number * * Returns the height of the viewport. + * + * Equivalent to `document.viewport.getDimensions().height`. **/ viewport.getHeight = define.curry('Height'); })(document.viewport); From b4015379a6861c27d4be83850357c45d13c9b3df Mon Sep 17 00:00:00 2001 From: dandean Date: Fri, 8 Jan 2010 11:42:58 -0800 Subject: [PATCH 111/502] doc: Merge/update old document.observe/stopObserving docs into source [#70 state:fixed_in_branch] --- src/dom/event.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 944b3b44a..dc3117629 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -767,13 +767,42 @@ /** * document.observe(eventName, handler) -> Element - * See [[Event.observe]]. + * + * Listens for the given event over the entire document. Can also be used + * for listening to `"dom:loaded"` event. + * + * `document.observe` is the document-wide version of [[Element#observe]]. + * Using `document.observe` is equivalent to + * `Event.observe(document, eventName, handler)`. + * + * ##### The `"dom:loaded"` event + * + * One really useful event generated by Prototype that you might want to + * observe on the document is `"dom:loaded"`. On supporting browsers it + * fires on `DOMContentLoaded` and on unsupporting browsers it simulates it + * using smart workarounds. If you used `window.onload` before you might + * want to switch to `dom:loaded` because it will fire immediately after + * the HTML document is fully loaded, but _before_ images on the page are + * fully loaded. The `load` event on `window` only fires after all page + * images are loaded, making it unsuitable for some initialization purposes + * like hiding page elements (so they can be shown later). + * + * ##### Example + * + * document.observe("dom:loaded", function() { + * // initially hide all containers for tab content + * $$('div.tabcontent').invoke('hide'); + * }); **/ observe: observe.methodize(), /** * document.stopObserving([eventName[, handler]]) -> Element - * See [[Event.stopObserving]]. + * + * Unregisters an event handler from the document. + * + * `document.stopObserving` is the document-wide version of + * [[Element.stopObserving]]. **/ stopObserving: stopObserving.methodize(), From 61394cb390a7e969680026de22ad1eb77813a441 Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 7 Jan 2010 01:13:26 -0800 Subject: [PATCH 112/502] doc: Merged/updated old docs for Element methods "down", "up", "next", "previous", "firstDescendant", "recursivelyCollect" and "siblings". [#71 state:fixed_in_branch] --- src/dom/dom.js | 426 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 425 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 17d48c310..ef00d8f2a 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -473,6 +473,34 @@ Element.Methods = { * specified by `property`. `property` has to be a _property_ (a method * won't do!) of `element` that points to a single DOM node (e.g., * `nextSibling` or `parentNode`). + * + * ##### More information + * + * This method is used internally by [[Element.ancestors]], + * [[Element.descendants]], [[Element.nextSiblings]], + * [[Element.previousSiblings]] and [[Element.siblings]] which offer really + * convenient way to grab elements, so directly accessing + * `Element.recursivelyCollect` should seldom be needed. However, if you are + * after something out of the ordinary, it is the way to go. + * + * Note that all of Prototype's DOM traversal methods ignore text nodes and return element + * nodes only. + * + * ##### Examples + * + *
      + *
    • + *
        + *
      • Golden Delicious

      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *
    • + *
    + * + * $('fruits').recursivelyCollect('firstChild'); + * // -> [li#apples, ul#list-of-apples, li#golden-delicious, p] **/ recursivelyCollect: function(element, property, maximumLength) { element = $(element); @@ -543,7 +571,27 @@ Element.Methods = { * Returns the first child that is an element. * * This is opposed to the `firstChild` DOM property, which will return - * any node, including text nodes. + * any node, including text nodes and comment nodes. + * + * ##### Examples + * + *
    + *
    + *
    + *
    + *
    + *
    + * + * $('australopithecus').firstDescendant(); + * // -> div#homo-herectus + * + * // the DOM property returns any first node + * $('homo-herectus').firstChild; + * // -> comment node "Latin is super" + * + * // this is what we want! + * $('homo-herectus').firstDescendant(); + * // -> div#homo-neanderthalensis **/ firstDescendant: function(element) { element = $(element).firstChild; @@ -591,6 +639,30 @@ Element.Methods = { * Element.siblings(@element) -> [Element...] * Collects all of element's siblings and returns them as an array of * elements. + * + * ##### More information + * + * Two elements are siblings if they have the same parent. So for example, + * the `head` and `body` elements are siblings (their parent is the `html` + * element). + * + * The returned array reflects the siblings' order in the document (e.g. an + * index of 0 refers to `element`'s topmost sibling). + * + * Note that all of Prototype's DOM traversal methods ignore text nodes and + * return element nodes only. + * + * ##### Examples + * + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    + * + * $('mutsu').siblings(); + * // -> [li#golden-delicious, li#mcintosh, li#ida-red] **/ siblings: function(element) { element = $(element); @@ -620,6 +692,96 @@ Element.Methods = { * is specified) that matches `expression`. If no `expression` is * provided, all ancestors are considered. If no ancestor matches these * criteria, `undefined` is returned. + * + * ##### More information + * + * The `Element.up` method is part of Prototype's ultimate DOM traversal + * toolkit (check out [[Element.down]], [[Element.next]] and + * [[Element.previous]] for some more Prototypish niceness). It allows + * precise index-based and/or CSS rule-based selection of any of `element`'s + * **ancestors**. + * + * As it totally ignores text nodes (it only returns elements), you don't + * have to worry about whitespace nodes. + * + * And as an added bonus, all elements returned are already extended + * (see [[Element.extended]]) allowing chaining: + * + * $(element).up(1).next('li', 2).hide(); + * + * Walking the DOM has never been that easy! + * + * ##### Arguments + * + * If no arguments are passed, `element`'s first ancestor is returned (this + * is similar to calling `parentNode` except `Element.up` returns an already + * extended element. + * + * If `index` is defined, `element`'s corresponding ancestor is returned. + * (This is equivalent to selecting an element from the array of elements + * returned by the method [[Element.ancestors]]). Note that the first element + * has an index of 0. + * + * If `expression` is defined, `Element.up` will return the first ancestor + * that matches it. + * + * If both `expression` and `index` are defined, `Element.up` will collect + * all the ancestors matching the given CSS expression and will return the + * one at the specified index. + * + * **In all of the above cases, if no descendant is found,** `undefined` + * **will be returned.** + * + * ### Examples + * + * + * [...] + * + *
      + *
    • + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *
    • + *
    + * + * + * + * Get the first ancestor of "#fruites": + * + * $('fruits').up(); + * // or: + * $('fruits').up(0); + * // -> body + * + * Get the third ancestor of "#mutsu": + * + * $('mutsu').up(2); + * // -> ul#fruits + * + * Get the first ancestor of "#mutsu" with the node name "li": + * + * $('mutsu').up('li'); + * // -> li#apples + * + * Get the first ancestor of "#mutsu" with the class name + * "keeps-the-doctor-away": + * + * $('mutsu').up('.keeps-the-doctor-away'); + * // -> li#apples + * + * Get the second ancestor of "#mutsu" with the node name "ul": + * + * $('mutsu').up('ul', 1); + * // -> ul#fruits + * + * Try to get the first ancestor of "#mutsu" with the node name "div": + * + * $('mutsu').up('div'); + * // -> undefined **/ up: function(element, expression, index) { element = $(element); @@ -638,6 +800,92 @@ Element.Methods = { * is specified) that matches `expression`. If no `expression` is * provided, all descendants are considered. If no descendant matches these * criteria, `undefined` is returned. + * + * ##### More information + * + * The `Element.down` method is part of Prototype's ultimate DOM traversal + * toolkit (check out [[Element.up]], [[Element.next]] and + * [[Element.previous]] for some more Prototypish niceness). It allows + * precise index-based and/or CSS rule-based selection of any of the + * element's **descendants**. + * + * As it totally ignores text nodes (it only returns elements), you don't + * have to worry about whitespace nodes. + * + * And as an added bonus, all elements returned are already extended + * (see [[Element.extend]]) allowing chaining: + * + * $(element).down(1).next('li', 2).hide(); + * + * Walking the DOM has never been that easy! + * + * ##### Arguments + * + * If no arguments are passed, `element`'s first descendant is returned (this + * is similar to calling `firstChild` except `Element.down` returns an + * extended element. + * + * If `index` is defined, `element`'s corresponding descendant is returned. + * (This is equivalent to selecting an element from the array of elements + * returned by the method [[Element.descendants]].) Note that the first + * element has an index of 0. + * + * If `expression` is defined, `Element.down` will return the first + * descendant that matches it. This is a great way to grab the first item in + * a list for example (just pass in 'li' as the method's first argument). + * + * If both `expression` and `index` are defined, `Element.down` will collect + * all the descendants matching the given CSS expression and will return the + * one at the specified index. + * + * **In all of the above cases, if no descendant is found,** `undefined` + * **will be returned.** + * + * ##### Examples + * + *
      + *
    • + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *
    • + *
    + * + * Get the first descendant of "#fruites": + * + * $('fruits').down(); + * // or: + * $('fruits').down(0); + * // -> li#apples + * + * Get the third descendant of "#fruits": + * + * $('fruits').down(3); + * // -> li#golden-delicious + * + * Get the first descendant of "#apples" with the node name "li": + * + * $('apples').down('li'); + * // -> li#golden-delicious + * + * Get the first descendant of "#apples" with the node name "li" and the + * class name "yummy": + * + * $('apples').down('li.yummy'); + * // -> li#mutsu + * + * Get the second descendant of "#fruits" with the class name "yummy": + * + * $('fruits').down('.yummy', 1); + * // -> li#mcintosh + * + * Try to get the ninety-ninth descendant of "#fruits": + * + * $('fruits').down(99); + * // -> undefined **/ down: function(element, expression, index) { element = $(element); @@ -655,6 +903,94 @@ Element.Methods = { * is specified) that matches `expression`. If no `expression` is * provided, all previous siblings are considered. If none matches these * criteria, `undefined` is returned. + * + * ##### More information + * + * The `Element.previous` method is part of Prototype's ultimate DOM + * traversal toolkit (check out [[Element.up]], [[Element.down]] and + * [[Element.next]] for some more Prototypish niceness). It allows precise + * index-based and/or CSS expression-based selection of any of `element`'s + * **previous siblings**. (Note that two elements are considered siblings if + * they have the same parent, so for example, the `head` and `body` elements + * are siblings—their parent is the `html` element.) + * + * As it totally ignores text nodes (it only returns elements), you don't + * have to worry about whitespace nodes. + * + * And as an added bonus, all elements returned are already extended (see + * [[Element.extend]]) allowing chaining: + * + * $(element).down('p').previous('ul', 2).hide(); + * + * Walking the DOM has never been that easy! + * + * ##### Arguments + * + * If no arguments are passed, `element`'s previous sibling is returned + * (this is similar as calling `previousSibling` except `Element.previous` + * returns an already extended element). + * + * If `index` is defined, `element`'s corresponding previous sibling is + * returned. (This is equivalent to selecting an element from the array of + * elements returned by the method [[Element.previousSiblings]]). Note that + * the sibling _right above_ `element` has an index of 0. + * + * If `expression` is defined, `Element.previous` will return the `element` + * first previous sibling that matches it. + * + * If both `expression` and `index` are defined, `Element.previous` will + * collect all of `element`'s previous siblings matching the given CSS + * expression and will return the one at the specified index. + * + * **In all of the above cases, if no previous sibling is found,** + * `undefined` **will be returned.** + * + * ##### Examples + * + *
      + *
    • + *

      Apples

      + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *

      An apple a day keeps the doctor away.

      + *
    • + *
    + * + * Get the first previous sibling of "#saying": + * + * $('saying').previous(); + * // or: + * $('saying').previous(0); + * // -> ul#list-of-apples + * + * Get the second previous sibling of "#saying": + * + * $('saying').previous(1); + * // -> h3 + * + * Get the first previous sibling of "#saying" with node name "h3": + * + * $('saying').previous('h3'); + * // -> h3 + * + * Get the first previous sibling of "#ida-red" with class name "yummy": + * + * $('ida-red').previous('.yummy'); + * // -> li#mutsu + * + * Get the second previous sibling of "#ida-red" with class name "yummy": + * + * $('ida-red').previous('.yummy', 1); + * // -> li#golden-delicious + * + * Try to get the sixth previous sibling of "#ida-red": + * + * $('ida-red').previous(5); + * // -> undefined **/ previous: function(element, expression, index) { element = $(element); @@ -677,6 +1013,94 @@ Element.Methods = { * is specified) that matches `expression`. If no `expression` is * provided, all following siblings are considered. If none matches these * criteria, `undefined` is returned. + * + * ##### More information + * + * The `Element.next` method is part of Prototype's ultimate DOM traversal + * toolkit (check out [[Element.up]], [[Element.down]] and + * [[Element.previous]] for some more Prototypish niceness). It allows + * precise index-based and/or CSS expression-based selection of any of + * `element`'s **following siblings**. (Note that two elements are considered + * siblings if they have the same parent, so for example, the `head` and + * `body` elements are siblings—their parent is the `html` element.) + * + * As it totally ignores text nodes (it only returns elements), you don't + * have to worry about whitespace nodes. + * + * And as an added bonus, all elements returned are already extended (see + * [[Element.extend]]) allowing chaining: + * + * $(element).down(1).next('li', 2).hide(); + * + * Walking the DOM has never been that easy! + * + * ##### Arguments + * + * If no arguments are passed, `element`'s following sibling is returned + * (this is similar as calling `nextSibling` except `Element.next` returns an + * already extended element). + * + * If `index` is defined, `element`'s corresponding following sibling is + * returned. (This is equivalent to selecting an element from the array of + * elements returned by the method [[Element.nextSiblings]]). Note that the + * sibling _right below_ `element` has an index of 0. + * + * If `expression` is defined, `Element.next` will return the `element` first + * following sibling that matches it. + * + * If both `expression` and `index` are defined, `Element.next` will collect + * all of `element`'s following siblings matching the given CSS expression + * and will return the one at the specified index. + * + * **In all of the above cases, if no following sibling is found,** + * `undefined` **will be returned.** + * + * ##### Examples + * + *
      + *
    • + *

      Apples

      + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *

      An apple a day keeps the doctor away.

      + *
    • + *
    + * + * Get the first sibling after "#title": + * + * $('title').next(); + * // or: + * $('title').next(0); + * // -> ul#list-of-apples + * + * Get the second sibling after "#title": + * + * $('title').next(1); + * // -> p#saying + * + * Get the first sibling after "#title" with node name "p": + * + * $('title').next('p'); + * // -> p#sayings + * + * Get the first sibling after "#golden-delicious" with class name "yummy": + * + * $('golden-delicious').next('.yummy'); + * // -> li#mcintosh + * + * Get the second sibling after "#golden-delicious" with class name "yummy": + * + * $('golden-delicious').next('.yummy', 1); + * // -> li#ida-red + * + * Try to get the first sibling after "#ida-red": + * + * $('ida-red').next(); + * // -> undefined **/ next: function(element, expression, index) { element = $(element); From 1cadce128cacf04ac6a871cba81309ebc923484f Mon Sep 17 00:00:00 2001 From: dandean Date: Sat, 9 Jan 2010 13:57:53 -0800 Subject: [PATCH 113/502] doc: Merge/update old Elements.Methods.wrap docs into source [#89 state:fixed_in_branch] --- src/dom/dom.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index ef00d8f2a..81c92f638 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -436,6 +436,63 @@ Element.Methods = { * element. Refer to the [[Element]] constructor for usage. * * Wraps an element inside another, then returns the wrapper. + * + * If the given element exists on the page, [[Element.wrap]] will wrap it in + * place — its position will remain the same. + * + * The `wrapper` argument can be _either_ an existing `HTMLElement` _or_ a + * string representing the tag name of an element to be created. The optional + * `attributes` argument can contain a list of attribute/value pairs that + * will be set on the wrapper using [[Element.writeAttribute]]. + * + * ##### Examples + * + * Original HTML: + * + * + * + * + * + * + * + * + * + * + *
    FooBar
    12
    + * + * JavaScript: + * + * // approach 1: + * var div = new Element('div', { 'class': 'table-wrapper' }); + * $('data').wrap(div); + * + * // approach 2: + * $('data').wrap('div', { 'class': 'table-wrapper' }); + * + * // Both examples are equivalent — they return the DIV. + * + * Resulting HTML: + * + *
    + * + * + * + * + * + * + * + * + * + *
    FooBar
    12
    + *
    + * + * ##### Warning + * + * Using [[Element.wrap]] as an instance method (e.g., `$('foo').wrap('p')`) + * causes errors in Internet Explorer when used on `textarea` elements. The + * `wrap` property is reserved on `textarea`'s as a proprietary extension to + * HTML. As a workaround, use the generic version instead + * (`Element.wrap('foo', 'p')`). **/ wrap: function(element, wrapper, attributes) { element = $(element); From c5ac98d3a11d62935e4576f3f11460646e2911f1 Mon Sep 17 00:00:00 2001 From: dandean Date: Sat, 9 Jan 2010 10:10:11 -0800 Subject: [PATCH 114/502] doc: Merge/update the old Element.hasAttribute docs into source [#90 state:fixed_in_branch] --- src/dom/dom.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 81c92f638..a01aab1ef 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2424,6 +2424,20 @@ Element._insertionTranslations = { })(); Element.Methods.Simulated = { + /** + * Element.hasAttribute(@element, attribute) -> Boolean + * + * Simulates the standard compliant DOM method + * [`hasAttribute`](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-ElHasAttr) + * for browsers missing it (Internet Explorer 6 and 7). + * + * ##### Example + * + * Prototype + * + * $('link').hasAttribute('href'); + * // -> true + **/ hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); From 86636c246a93a3988154e4540f2ba0f01a09705c Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 22:41:12 -0800 Subject: [PATCH 115/502] doc: Merge/update old docs for Element.Methods attribute stuff into source [#82 state:fixed_in_branch] --- src/dom/dom.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index a01aab1ef..0d34fd98d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1240,7 +1240,27 @@ Element.Methods = { /** * Element.readAttribute(@element, attributeName) -> String | null * - * Returns the value of `element`'s attribute with the given name. + * Returns the value of `element`'s `attribute` or `null` if `attribute` has + * not been specified. + * + * This method serves two purposes. First it acts as a simple wrapper around + * `getAttribute` which isn't a "real" function in Safari and Internet + * Explorer (it doesn't have `.apply` or `.call` for instance). Secondly, it + * cleans up the horrible mess Internet Explorer makes when handling + * attributes. + * + * ##### Examples + * + * + * + * $('tag').readAttribute('href'); + * // -> '/tags/prototype' + * + * $('tag').readAttribute('title'); + * // -> 'view related bookmarks.' + * + * $('tag').readAttribute('my_widget'); + * // -> 'some info.' **/ readAttribute: function(element, name) { element = $(element); @@ -1260,7 +1280,7 @@ Element.Methods = { * Element.writeAttribute(@element, attribute[, value = true]) -> Element * Element.writeAttribute(@element, attributes) -> Element * - * Adds, changes, or removes attributes passed as either a hash or a + * Adds, specifies or removes attributes passed as either a hash or a * name/value pair. **/ writeAttribute: function(element, name, value) { From 21f707de3de82b820f0e2f058b9de9506d986a5a Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 22:11:43 -0800 Subject: [PATCH 116/502] doc: Merge/update old docs for Element.Methods clipping stuff into source [#83 state:fixed_in_branch] --- src/dom/dom.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 0d34fd98d..23ebba8b6 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1657,6 +1657,44 @@ Element.Methods = { * * Simulates the poorly-supported CSS `clip` property by setting `element`'s * `overflow` value to `hidden`. + * + * To undo clipping, use [[Element.undoClipping]]. + * + * The visible area is determined by `element`'s width and height. + * + * ##### Example + * + *
    + * example + *
    + * + * $('framer').makeClipping().setStyle({width: '100px', height: '100px'}); + * // -> HTMLElement + * + * Click me to try it out. + * + *
    + * example + *
    + * + * **/ makeClipping: function(element) { element = $(element); @@ -1672,6 +1710,41 @@ Element.Methods = { * * Sets `element`'s CSS `overflow` property back to the value it had * _before_ [[Element.makeClipping]] was applied. + * + * ##### Example + * + *
    + * example + *
    + * + * $('framer').undoClipping(); + * // -> HTMLElement (and sets the CSS overflow property to its original value). + * + * Click me to try it out. + * + *
    + * example + *
    + * + * **/ undoClipping: function(element) { element = $(element); From cb27017e0f7d1221fe2787830ca93d5044a64161 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 21:46:01 -0800 Subject: [PATCH 117/502] doc: Merge/update old Element.Methods.match docs into source [#85 state:fixed_in_branch] --- src/dom/dom.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 23ebba8b6..71e03dd7d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -732,6 +732,28 @@ Element.Methods = { * - selector (String): A CSS selector. * * Checks if `element` matches the given CSS selector. + * + * ##### Examples + * + *
      + *
    • + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *
    • + *
    + * + * $('fruits').match('ul'); + * // -> true + * + * $('mcintosh').match('li#mcintosh.yummy'); + * // -> true + * + * $('fruits').match('p'); + * // -> false **/ match: function(element, selector) { element = $(element); From 664f03d0f7bef5bf27102231188caca19f11ed02 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 13:55:11 -0800 Subject: [PATCH 118/502] doc: Merge/update old Element.Methods.replace docs into source [#86 state:fixed_in_branch] --- src/dom/dom.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 71e03dd7d..7dcb10771 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -336,6 +336,67 @@ Element.Methods = { * * Keep in mind that this method returns the element that has just been * removed — not the element that took its place. + * + * `newContent` can be either plain text, an HTML snippet or any JavaScript + * object which has a `toString()` method. + * + * If `newContent` contains any `'); + * // -> HTMLElement (ul#favorite) and prints "removed!" in an alert dialog. + * + * $('fruits').innerHTML + * // -> '

    Melon, oranges and grapes.

    ' + * + * With plain text: + * + * $('still-first').replace('Melon, oranges and grapes.'); + * // -> HTMLElement (p#still-first) + * + * $('fruits').innerHTML + * // -> 'Melon, oranges and grapes.' + * + * Finally, relying on the `toString()` method: + * + * $('fruits').replace(123); + * // -> HTMLElement + * + * $('food').innerHTML; + * // -> '123' + * + * ##### Warning + * + * Using [[Element.replace]] as an instance method (e.g., + * `$('foo').replace('

    Bar

    ')`) causes errors in Opera 9 when used on + * `input` elements. The `replace` property is reserved on `input` elements + * as part of [Web Forms 2](http://www.whatwg.org/specs/web-forms/current-work/). + * As a workaround, use the generic version instead + * (`Element.replace('foo', '

    Bar

    ')`). + * **/ replace: function(element, content) { element = $(element); From 748e48a98c6244dc5bea1164618e1a89c376646c Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 13:32:18 -0800 Subject: [PATCH 119/502] doc: Merge/update old docs for Element.Methods.inspect into source [#84 state:fixed_in_branch] --- src/dom/dom.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 7dcb10771..11db8e82c 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -571,6 +571,24 @@ Element.Methods = { * Element.inspect(@element) -> String * * Returns the debug-oriented string representation of `element`. + * + * For more information on `inspect` methods, see [[Object.inspect]]. + * + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • + *
    + * + * $('golden-delicious').inspect(); + * // -> '
  • ' + * + * $('mutsu').inspect(); + * // -> '
  • ' + * + * $('mutsu').next().inspect(); + * // -> '
  • ' **/ inspect: function(element) { element = $(element); From ed5f14493a43294152aee53edc1081a2cb1a9a30 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 09:40:03 -0800 Subject: [PATCH 120/502] doc: Merge/update old Elements.Methods.visible docs into source [#88 state:fixed_in_branch] --- src/dom/dom.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 11db8e82c..c887f497a 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -166,10 +166,39 @@ Element.cache = { }; **/ Element.Methods = { /** - * Element.visible(@element) -> boolean + * Element.visible(@element) -> Boolean * * Tells whether `element` is visible (i.e., whether its inline `display` * CSS property is set to `none`. + * + * ##### Examples + * + *
    + * + * + * $('visible').visible(); + * // -> true + * + * $('hidden').visible(); + * // -> false + * + * ##### Notes + * + * Styles applied via a CSS stylesheet are _not_ taken into consideration. + * Note that this is not a Prototype limitation, it is a CSS limitation. + * + * + * + * […] + * + *
    + * + * $('hidden-by-css').visible(); + * // -> true **/ visible: function(element) { return $(element).style.display != 'none'; From 02d94a2975a2a5652a2a0f10703f379893354b98 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 11 Jan 2010 23:07:37 -0800 Subject: [PATCH 121/502] doc: Merge/update old Elements.Methods docs for class name stuff into source. [#81 state:fixed_in_branch] --- src/dom/dom.js | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index c887f497a..bf73ced06 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1468,7 +1468,17 @@ Element.Methods = { /** * Element.hasClassName(@element, className) -> Boolean * - * Checks whether `element` has the given CSS class name. + * Checks for the presence of CSS class `className` on `element`. + * + * ##### Examples + * + *
    + * + * $('mutsu').hasClassName('fruit'); + * // -> true + * + * $('mutsu').hasClassName('vegetable'); + * // -> false **/ hasClassName: function(element, className) { if (!(element = $(element))) return; @@ -1508,7 +1518,17 @@ Element.Methods = { /** * Element.removeClassName(@element, className) -> Element * - * Removes a CSS class from `element`. + * Removes CSS class `className` from `element`. + * + * ##### Examples + * + *
    + * + * $('mutsu').removeClassName('food'); + * // -> Element + * + * $('mutsu').classNames; + * // -> 'apple fruit' **/ removeClassName: function(element, className) { if (!(element = $(element))) return; @@ -1520,7 +1540,20 @@ Element.Methods = { /** * Element.toggleClassName(@element, className) -> Element * - * Toggles the presence of a CSS class on `element`. + * Toggles the presence of CSS class `className` on `element`. + * + * ##### Examples + * + *
    + * + * $('mutsu').hasClassName('fruit'); + * // -> false + * + * $('mutsu').toggleClassName('fruit'); + * // -> element + * + * $('mutsu').hasClassName('fruit'); + * // -> true **/ toggleClassName: function(element, className) { if (!(element = $(element))) return; From 79186e53c68071c93dfb67789bd7e1d7d524233e Mon Sep 17 00:00:00 2001 From: Mike Rumble Date: Tue, 17 Nov 2009 18:45:09 +0000 Subject: [PATCH 122/502] doc: String#stripTags examples [#131 state:fixed_in_branch] --- src/lang/string.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lang/string.js b/src/lang/string.js index e581f6322..a860cabad 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -137,6 +137,9 @@ Object.extend(String.prototype, (function() { * `span`, and `abbr`. It _will not_ strip namespace-prefixed tags such * as `h:table` or `xsl:template`. * + * Watch out for `'.stripTags(); + * // -> 'a linkalert("hello world!");' + * + * 'a link'.stripScripts().stripTags(); + * // -> 'a link' **/ function stripTags() { return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); From 587319a72f40bf6ff78d3badbe99fffb31dffd22 Mon Sep 17 00:00:00 2001 From: Mike Rumble Date: Tue, 17 Nov 2009 18:58:25 +0000 Subject: [PATCH 123/502] doc: String#toArray examples [#132 state:fixed_in_branch] --- src/lang/string.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/lang/string.js b/src/lang/string.js index a860cabad..bcedf32c6 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -295,6 +295,14 @@ Object.extend(String.prototype, (function() { * * Splits the string character-by-character and returns an array with * the result. + * + *
    Examples
    + * + * 'a'.toArray(); + * // -> ['a'] + * + * 'hello world!'.toArray(); + * // -> ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] **/ function toArray() { return this.split(''); From 7d14495864dc52b0776c361edec05bb4d4a5f47e Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Tue, 12 Jan 2010 12:56:06 +0100 Subject: [PATCH 124/502] doc: Merge/update old docs for various String scanning functions into source [#130 state:fixed_in_branch] --- src/lang/string.js | 115 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 12 deletions(-) diff --git a/src/lang/string.js b/src/lang/string.js index bcedf32c6..a39d1a9bf 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -39,14 +39,50 @@ Object.extend(String.prototype, (function() { /** * String#gsub(pattern, replacement) -> String * - * Returns the string with every occurence of a given pattern replaced by either - * a regular string, the returned value of a function or a [[Template]] string. + * Returns the string with _every_ occurence of a given pattern replaced by either a + * regular string, the returned value of a function or a [[Template]] string. * The pattern can be a string or a regular expression. - * - *
    Example
    - * - * ""hello".gsub(/([aeiou])/, '<#{1}>'); - * // => "hll" + * + * If its second argument is a string [[String#gsub]] works just like the native JavaScript + * method `replace()` set to global match. + * + * var mouseEvents = 'click dblclick mousedown mouseup mouseover mousemove mouseout'; + * + * mouseEvents.gsub(' ', ', '); + * // -> 'click, dblclick, mousedown, mouseup, mouseover, mousemove, mouseout' + * + * mouseEvents.gsub(/\s+/, ', '); + * // -> 'click, dblclick, mousedown, mouseup, mouseover, mousemove, mouseout' + * + * If you pass it a function, it will be invoked for every occurrence of the pattern + * with the match of the current pattern as its unique argument. Note that this argument + * is the returned value of the `match()` method called on the current pattern. It is + * in the form of an array where the first element is the entire match and every subsequent + * one corresponds to a parenthesis group in the regex. + * + * mouseEvents.gsub(/\w+/, function(match){ return 'on' + match[0].capitalize() }); + * // -> 'onClick onDblclick onMousedown onMouseup onMouseover onMousemove onMouseout' + * + * var markdown = '![a pear](/img/pear.jpg) ![an orange](/img/orange.jpg)'; + * + * markdown.gsub(/!\[(.*?)\]\((.*?)\)/, function(match) { + * return '' + match[1] + ''; + * }); + * // -> 'a pear an orange' + * + * Lastly, you can pass [[String#gsub]] a [[Template]] string in which you can also access + * the returned value of the `match()` method using the ruby inspired notation: `#{0}` + * for the first element of the array, `#{1}` for the second one, and so on. + * So our last example could be easily re-written as: + * + * markdown.gsub(/!\[(.*?)\]\((.*?)\)/, '#{1}'); + * // -> 'a pear an orange' + * + * If you need an equivalent to [[String#gsub]] but without global match set on, try [[String#sub]]. + * + * ##### Note + * + * Do _not_ use the `"g"` flag on the regex as this will create an infinite loop. **/ function gsub(pattern, replacement) { var result = '', source = this, match; @@ -75,14 +111,46 @@ Object.extend(String.prototype, (function() { /** * String#sub(pattern, replacement[, count = 1]) -> String * - * Returns a string with the first count occurrences of pattern replaced by either + * Returns a string with the _first_ `count` occurrences of `pattern` replaced by either * a regular string, the returned value of a function or a [[Template]] string. - * The pattern can be a string or a regular expression. + * `pattern` can be a string or a regular expression. + * + * Unlike [[String#gsub]], [[String#sub]] takes a third optional parameter which specifies + * the number of occurrences of the pattern which will be replaced. + * If not specified, it will default to 1. + * + * Apart from that, [[String#sub]] works just like [[String#gsub]]. + * Please refer to it for a complete explanation. + * + * ##### Examples * - *
    Example
    + * var fruits = 'apple pear orange'; + * + * fruits.sub(' ', ', '); + * // -> 'apple, pear orange' + * + * fruits.sub(' ', ', ', 1); + * // -> 'apple, pear orange' + * + * fruits.sub(' ', ', ', 2); + * // -> 'apple, pear, orange' + * + * fruits.sub(/\w+/, function(match){ return match[0].capitalize() + ',' }, 2); + * // -> 'Apple, Pear, orange' + * + * var markdown = '![a pear](/img/pear.jpg) ![an orange](/img/orange.jpg)'; + * + * markdown.sub(/!\[(.*?)\]\((.*?)\)/, function(match) { + * return '' + match[1] + ''; + * }); + * // -> 'a pear ![an orange](/img/orange.jpg)' + * + * markdown.sub(/!\[(.*?)\]\((.*?)\)/, '#{1}'); + * // -> 'a pear ![an orange](/img/orange.jpg)' * - * "20091201".sub(/^(\d{4})(\d{2})(\d{2})$/, "#{1}-#{2}-#{3}"); - * // => "2009-12-01" + * ##### Note + * + * Do _not_ use the `"g"` flag on the regex as this will create an infinite loop. **/ function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); @@ -100,6 +168,29 @@ Object.extend(String.prototype, (function() { * Allows iterating over every occurrence of the given pattern (which can be a * string or a regular expression). * Returns the original string. + * + * Internally just calls [[String#gsub]] passing it `pattern` and `iterator` as arguments. + * + * ##### Examples + * + * 'apple, pear & orange'.scan(/\w+/, alert); + * // -> 'apple pear orange' (and displays 'apple', 'pear' and 'orange' in three successive alert dialogs) + * + * Can be used to populate an array: + * + * var fruits = []; + * 'apple, pear & orange'.scan(/\w+/, function(match) { fruits.push(match[0]) }); + * fruits.inspect() + * // -> ['apple', 'pear', 'orange'] + * + * or even to work on the DOM: + * + * 'failure-message, success-message & spinner'.scan(/(\w|-)+/, Element.toggle) + * // -> 'failure-message, success-message & spinner' (and toggles the visibility of each DOM element) + * + * ##### Note + * + * Do _not_ use the `"g"` flag on the regex as this will create an infinite loop. **/ function scan(pattern, iterator) { this.gsub(pattern, iterator); From 20e5c251075ae992343962d708c77d6500e21def Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 12 Jan 2010 10:27:39 -0800 Subject: [PATCH 125/502] doc: Merge/update old Element.Methods.select docs into source. [#76 state:fixed_in_branch] --- src/dom/dom.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index bf73ced06..049dee9e6 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1309,6 +1309,50 @@ Element.Methods = { * * Takes an arbitrary number of CSS selectors and returns an array of * descendants of `element` that match any of them. + * + * This method is very similar to [[$$]] but can be used within the context + * of one element, rather than the whole document. The supported CSS syntax + * is identical, so please refer to the [[$$]] docs for details. + * + * ##### Examples + * + *
      + *
    • + *

      Apples

      + *
        + *
      • Golden Delicious
      • + *
      • Mutsu
      • + *
      • McIntosh
      • + *
      • Ida Red
      • + *
      + *

      An apple a day keeps the doctor away.

      + *
    • + *
    + * + * $('apples').select('[title="yummy!"]'); + * // -> [h3, li#golden-delicious, li#mutsu] + * + * $('apples').select( 'p#saying', 'li[title="yummy!"]'); + * // -> [li#golden-delicious, li#mutsu, p#saying] + * + * $('apples').select('[title="disgusting!"]'); + * // -> [] + * + * ##### Tip + * [[Element.select]] can be used as a pleasant alternative to the native + * method `getElementsByTagName`: + * + * var nodes = $A(someUL.getElementsByTagName('li')).map(Element.extend); + * var nodes2 = someUL.select('li'); + * + * In the first example, you must explicitly convert the result set to an + * [[Array]] (so that Prototype's [[Enumerable]] methods can be used) and + * must manually call [[Element.extend]] on each node (so that custom + * instance methods can be used on the nodes). [[Element.select]] takes care + * of both concerns on its own. + * + * If you're using 1.6 or above (and the performance optimizations therein), + * the speed difference between these two examples is negligible. **/ select: function(element) { element = $(element); From a28a56dacd94f4ea4764ddfe9b7a5db45835c6aa Mon Sep 17 00:00:00 2001 From: Avram Eisner Date: Tue, 26 Jan 2010 23:10:04 -0800 Subject: [PATCH 126/502] doc: Merge/update old Ajax.PeriodicalUpdater docs into source [#66 state:fixed_in_branch] --- src/ajax/periodical_updater.js | 147 +++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/ajax/periodical_updater.js b/src/ajax/periodical_updater.js index d5fa4ef10..0cbb41d9a 100644 --- a/src/ajax/periodical_updater.js +++ b/src/ajax/periodical_updater.js @@ -56,6 +56,153 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { * [[Ajax section]] for more information. * * Creates a new `Ajax.PeriodicalUpdater`. + * + * Periodically performs an AJAX request and updates a container's contents + * based on the response text. Offers a mechanism for "decay," which lets it + * trigger at widening intervals while the response is unchanged. + * + * This object addresses the common need of periodical update, which is used + * by all sorts of "polling" mechanisms (e.g. in an online chatroom or an + * online mail client). + * + * The basic idea is to run a regular [[Ajax.Updater]] at + * regular intervals, monitoring changes in the response text if the `decay` + * option (see below) is active. + * + * ##### Additional options + * + * `Ajax.PeriodicalUpdater` features all the common options and callbacks + * (see the [[Ajax section]] for more information), plus those added by + * [[Ajax.Updater]]. It also provides two new options that deal with the + * original period, and its decay rate (how Rocket Scientist does that make + * us sound, uh?!). + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    OptionDefaultDescription
    frequency2Okay, this is not a frequency (e.g 0.5Hz), but a period (i.e. a number of seconds). + * Don't kill me, I didn't write this one! This is the minimum interval at which AJAX + * requests are made. You don't want to make it too short (otherwise you may very well + * end up with multiple requests in parallel, if they take longer to process and return), + * but you technically can provide a number below one, e.g. 0.75 second.
    decay1This controls the rate at which the request interval grows when the response is + * unchanged. It is used as a multiplier on the current period (which starts at the original + * value of the frequency parameter). Every time a request returns an unchanged + * response text, the current period is multiplied by the decay. Therefore, the default + * value means regular requests (no change of interval). Values higher than one will + * yield growing intervals. Values below one are dangerous: the longer the response text + * stays the same, the more often you'll check, until the interval is so short your browser + * is left with no other choice than suicide. Note that, as soon as the response text + * does change, the current period resets to the original one.
    + * + * To better understand decay, here is a small sequence of calls from the following example: + * + * new Ajax.PeriodicalUpdater('items', '/items', { + * method: 'get', frequency: 3, decay: 2 + * }); + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Call#When?Decay beforeResponse changed?Decay afterNext periodComments
    100:002n/a13Response is deemed changed, since there is no prior response to compare to!
    200:031yes13Response did change again: we "reset" to 1, which was already the decay.
    300:061no26Response didn't change: decay augments by the decay option factor: + * we're waiting longer now…
    400:122no412Still no change, doubling again.
    500:244no824Jesus, is this thing going to change or what?
    600:488yes13Ah, finally! Resetting decay to 1, and therefore using the original period.
    + * + * ##### Disabling and re-enabling a `PeriodicalUpdater` + * + * You can pull the brake on a running `PeriodicalUpdater` by simply calling + * its `stop` method. If you wish to re-enable it later, just call its `start` + * method. Both take no argument. + * + * ##### Beware! Not a specialization! + * + * `Ajax.PeriodicalUpdater` is not a specialization of [[Ajax.Updater]], + * despite its name. When using it, do not expect to be able to use methods + * normally provided by [[Ajax.Request]] and "inherited" by `Ajax.Updater`, + * such as `evalJSON` or `getHeader`. Also the `onComplete` callback is + * hijacked to be used for update management, so if you wish to be notified + * of every successful request, use `onSuccess` instead (beware: it will get + * called *before* the update is performed). **/ initialize: function($super, container, url, options) { $super(options); From 9436d60115b927763a3ed4dd65a740f07e22ad4f Mon Sep 17 00:00:00 2001 From: Avram Eisner Date: Tue, 26 Jan 2010 22:02:33 -0800 Subject: [PATCH 127/502] doc: Merge/update old Elements.fire docs into source [#75 state:fixed_in_branch] --- src/dom/event.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/dom/event.js b/src/dom/event.js index dc3117629..b5cdf7564 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -728,6 +728,45 @@ /** * Element.fire(@element, eventName[, memo[, bubble = true]]) -> Event * See [[Event.fire]]. + * + * Fires a custom event with the current element as its target. + * + * [[Element.fire]] creates a custom event with the given name, then triggers + * it on the given element. The custom event has all the same properties + * and methods of native events. Like a native event, it will bubble up + * through the DOM unless its propagation is explicitly stopped. + * + * The optional second argument will be assigned to the `memo` property of + * the event object so that it can be read by event handlers. + * + * Custom events are dispatched synchronously: [[Element.fire]] waits until + * the event finishes its life cycle, then returns the event itself. + * + * ##### Note + * + * [[Element.fire]] does not support firing native events. All custom event + * names _must_ be namespaced (using a colon). This is to avoid custom + * event names conflicting with non-standard native DOM events such as + * `mousewheel` and `DOMMouseScroll`. + * + * ##### Examples + * + * document.observe("widget:frobbed", function(event) { + * console.log("Element with ID (" + event.target.id + + * ") frobbed widget #" + event.memo.widgetNumber + "."); + * }); + * + * var someNode = $('foo'); + * someNode.fire("widget:frobbed", { widgetNumber: 19 }); + * + * //-> "Element with ID (foo) frobbed widget #19." + * + * ##### Tip + * + * Events that have been stopped with [[Event.stop]] will have a boolean + * `stopped` property set to true. Since [[Element.fire]] returns the custom + * event, you can inspect this property to determine whether the event was + * stopped. **/ fire: fire, From edd82b34cdf985d47f1c474addd2968cf3bb3823 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Tue, 26 Jan 2010 21:52:59 -0800 Subject: [PATCH 128/502] doc: Merge/update old Form.Element.getValue and present docs into source [#108 state:fixed_in_branch] --- src/dom/form.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/dom/form.js b/src/dom/form.js index 1dc1deb78..3d8846370 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -320,6 +320,33 @@ Form.Element.Methods = { * return an array of values. * * The global shortcut for this method is [[$F]]. + * + * ##### How to reference form controls by their _name_ + * + * This method is consistent with other DOM extensions in that it requires an + * element **ID** as the string argument, not the name of the + * form control (as some might think). If you want to reference controls by + * their names, first find the control the regular JavaScript way and use the + * node itself instead of an ID as the argument. + * + * For example, if you have an `input` named "company" in a `form` with an + * ID "contact": + * + * var form = $('contact'); + * var input = form['company']; + * + * Form.Element.getValue(input); + * + * // but, the preferred call is: + * $(input).getValue(); // we used the $() method so the node gets extended + * + * // you can also use the shortcut + * $F(input); + * + * ##### Note + * + * An error is thrown ("element has no properties") if the `element` argument + * is an unknown ID. **/ getValue: function(element) { element = $(element); @@ -353,6 +380,46 @@ Form.Element.Methods = { * Form.Element.present(@element) -> Element * * Returns `true` if a text input has contents, `false` otherwise. + * + * ##### Example + * + * This method is very handy in a generic form validation routine. + * On the following form's submit event, the presence of each text input is + * checked and lets the user know if they left a text input blank. + * + *
    + *
    + * User Details + *

    Please fill out the following fields:

    + *

    + * + * + *

    + *

    + * + * + *

    + * + *
    + *
    + * + * **/ present: function(element) { return $(element).value != ''; From fcba3f01e89bddbfcbc215c2c73ed51652cca4e9 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Tue, 26 Jan 2010 12:50:42 -0800 Subject: [PATCH 129/502] doc: Merge/update old docs into the source [#113 state:fixed_in_branch] --- src/dom/form.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dom/form.js b/src/dom/form.js index 3d8846370..aa0e1d7f7 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -472,6 +472,9 @@ var Field = Form.Element; /** section: DOM, related to: Form * $F(element) -> String | Array + * + * Returns the value of a form control. This is a convenience alias of + * [[Form.Element.getValue]]. Refer to it for full details. **/ var $F = Form.Element.Methods.getValue; From 7b06652c09341edde6da9000098b6dfddafc9cfe Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 26 Jan 2010 13:29:39 -0800 Subject: [PATCH 130/502] doc: Merge/update old Event.pointerX and pointerY docs into source [#97 state:fixed_in_branch] --- src/dom/event.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index b5cdf7564..d1ebd6ad2 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -180,8 +180,9 @@ * Returns the absolute horizontal position of the pointer for a mouse * event. * - * Note that this position is absolute on the _page_, not on the - * _viewport_. + * Note that this position is absolute on the ``, not on the + * viewport: scrolling right increases the returned value for events on + * the same viewport location. **/ function pointerX(event) { var docElement = document.documentElement, @@ -198,8 +199,9 @@ * Returns the absolute vertical position of the pointer for a mouse * event. * - * Note that this position is absolute on the _page_, not on the - * _viewport_. + * Note that this position is absolute on the ``, not on the + * viewport: scrolling down increases the returned value for events on + * the same viewport location. **/ function pointerY(event) { var docElement = document.documentElement, From da3fd7c8a6febe22c3704a0f7ff8b8d4e387392f Mon Sep 17 00:00:00 2001 From: dandean Date: Fri, 22 Jan 2010 16:56:37 -0800 Subject: [PATCH 131/502] doc: Merge/update old ObjectRange docs into source [#121 state:fixed_in_branch] --- src/lang/range.js | 85 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/lang/range.js b/src/lang/range.js index 6b209b268..208637df6 100644 --- a/src/lang/range.js +++ b/src/lang/range.js @@ -4,36 +4,39 @@ * * A succession of values. * - * An `ObjectRange` can model a range of any value that implements a `succ` + * An [[ObjectRange]] can model a range of any value that implements a `succ` * method (which links that value to its "successor"). * * Prototype provides such a method for [[Number]] and [[String]], but you * are (of course) welcome to implement useful semantics in your own objects, * in order to enable ranges based on them. * - * `ObjectRange` mixes in [[Enumerable]], which makes ranges very versatile. + * [[ObjectRange]] mixes in [[Enumerable]], which makes ranges very versatile. * It takes care, however, to override the default code for `include`, to * achieve better efficiency. * - * While `ObjectRange` does provide a constructor, the preferred way to obtain + * While [[ObjectRange]] does provide a constructor, the preferred way to obtain * a range is to use the [[$R]] utility function, which is strictly equivalent * (only way more concise to use). + * + * See [[$R]] for more information. **/ /** section: Language * $R(start, end[, exclusive = false]) -> ObjectRange * - * Creates a new `ObjectRange` object. This method is a convenience wrapper around the - * [`ObjectRange`](/api/objectRange) constructor, but `$R` is the preferred alias. + * Creates a new [[ObjectRange]] object. This method is a convenience wrapper + * around the [[ObjectRange]] constructor, but [[$R]] is the preferred alias. * - * [`ObjectRange`](/api/objectRange) instances represent a range of consecutive values, - * be they numerical, textual, or of another type that semantically supports value ranges. - * See the type's documentation for further details, and to discover how your own objects - * can support value ranges. + * [[ObjectRange]] instances represent a range of consecutive values, be they + * numerical, textual, or of another type that semantically supports value + * ranges. See the type's documentation for further details, and to discover + * how your own objects can support value ranges. * - * The `$R` function takes exactly the same arguments as the original constructor: the - * **lower and upper bounds** (value of the same, proper type), and **whether the upper - * bound is exclusive** or not. By default, the upper bound is inclusive. + * The [[$R]] function takes exactly the same arguments as the original + * constructor: the **lower and upper bounds** (value of the same, proper + * type), and **whether the upper bound is exclusive** or not. By default, the + * upper bound is inclusive. * * ##### Examples * @@ -53,14 +56,28 @@ * // invoked 10 times for value = 0 to 9 * }); * - * Note that `ObjectRange` mixes in the [`Enumerable`](/api/enumerable) module: this - * makes it easy to convert a range to an `Array` (`Enumerable` provides the [`toArray`](/api/enumerable/toArray) - * method, which makes the [`$A`](dollar-a) conversion straightforward), or to iterate - * through values. (Note, however, that getting the bounds back will be more efficiently - * done using the `start` and `end` properties than calling the [`min()`](/api/enumerable/min) - * and [`max()`](/api/enumerable/max) methods). + * Note that [[ObjectRange]] mixes in the [[Enumerable]] module: this makes it + * easy to convert a range to an [[Array]] ([[Enumerable]] provides the + * [[Enumerable#toArray]] method, which makes the [[$A]] conversion + * straightforward), or to iterate through values. (Note, however, that getting + * the bounds back will be more efficiently done using the + * [[ObjectRange#start]] and [[ObjectRange#end]] properties than calling the + * [[Enumerable#min]] and [[Enumerable#max]] methods). + * + * ##### Warning + * + * **Be careful with [[String]] ranges**: as described in its [[String#succ]] + * method, it does not use alphabetical boundaries, but goes all the way + * through the character table: + * + * $A($R('a', 'e')) + * // -> ['a', 'b', 'c', 'd', 'e'], no surprise there + * + * $A($R('ax', 'ba')) + * // -> Ouch! Humongous array, starting as ['ax', 'ay', 'az', 'a{', 'a|', 'a}', 'a~'...] + * + * See [[ObjectRange]] for more information. **/ - function $R(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } @@ -69,13 +86,21 @@ var ObjectRange = Class.create(Enumerable, (function() { /** * new ObjectRange(start, end[, exclusive = false]) * - * Creates a new `ObjectRange`. + * Creates a new [[ObjectRange]]. * * The `exclusive` argument specifies whether `end` itself is a part of the * range. **/ function initialize(start, end, exclusive) { + /** + * ObjectRange#start -> ? + * The lower bounding value of the range. + **/ this.start = start; + /** + * ObjectRange#end -> ? + * The upper bounding value of the range. + **/ this.end = end; this.exclusive = exclusive; } @@ -92,6 +117,26 @@ var ObjectRange = Class.create(Enumerable, (function() { * ObjectRange#include(value) -> Boolean * * Determines whether the value is included in the range. + * + * This assumes the values in the range have a valid strict weak ordering + * (have valid semantics for the `<` operator). While [[ObjectRange]] mixes + * in [[Enumerable]], this method overrides the default version of + * [[Enumerable#include]], and is much more efficient (it uses a maximum of + * two comparisons). + * + * ##### Examples + * + * $R(1, 10).include(5); + * // -> true + * + * $R('a', 'h').include('x'); + * // -> false + * + * $R(1, 10).include(10); + * // -> true + * + * $R(1, 10, true).include(10); + * // -> false **/ function include(value) { if (value < this.start) From 0505a771242292a1b14cf37378473038daa0bf76 Mon Sep 17 00:00:00 2001 From: Avram Eisner Date: Mon, 18 Jan 2010 20:07:20 -0800 Subject: [PATCH 132/502] doc: Merge/update old Form.Element.serialize docs into source [#109 state:fixed_in_branch] --- src/dom/form.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/dom/form.js b/src/dom/form.js index aa0e1d7f7..e0f2198c5 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -297,6 +297,18 @@ Form.Element.Methods = { * * Returns a URL-encoded string representation of a form control in the * `name=value` format. + * + * The result of this method is a string suitable for Ajax requests. However, + * it serializes only a single element - if you need to serialize the whole + * form use [[Form.serialize]] instead. + * + * ##### Notes + * + * Serializing a disabled control or a one without a name will always result + * in an empty string. + * + * If you simply need an element's value for reasons other than Ajax + * requests, use [[Form.Element.getValue]] instead. **/ serialize: function(element) { element = $(element); From 8f08109fbf33c05708087830c7b6990962370ce0 Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 14 Jan 2010 19:44:55 -0800 Subject: [PATCH 133/502] doc: Merge/update old docs for Element.Methods insert/remove/update methods into source [#80 state:fixed_in_branch] --- src/dom/dom.js | 108 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 10 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 049dee9e6..3bef48164 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -242,6 +242,29 @@ Element.Methods = { * Element.remove(@element) -> Element * * Completely removes `element` from the document and returns it. + * + * If you would rather just hide the element and keep it around for further + * use, try [[Element.hide]] instead. + * + * ##### Examples + * + * // Before: + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    + * + * $('mutsu').remove(); + * // -> HTMLElement (and removes li#mutsu) + * + * // After: + *
      + *
    • Golden Delicious
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    **/ remove: function(element) { element = $(element); @@ -265,11 +288,72 @@ Element.Methods = { * If `newContent` is omitted, the element's content is blanked out (i.e., * replaced with an empty string). * - * If `newContent` is a string and contains one or more inline `'); + * // -> HTMLElement (and prints "updated!" in an alert dialog). + * $('fruits').innerHTML; + * // -> '

    Kiwi, banana and apple.

    ' + * + * Relying on the `toString()` method: + * + * $('fruits').update(123); + * // -> HTMLElement + * $('fruits').innerHTML; + * // -> '123' + * + * Finally, you can do some pretty funky stuff by defining your own + * `toString()` method on your custom objects: + * + * var Fruit = Class.create({ + * initialize: function(fruit){ + * this.fruit = fruit; + * }, + * toString: function(){ + * return 'I am a fruit and my name is "' + this.fruit + '".'; + * } + * }); + * var apple = new Fruit('apple'); + * + * $('fruits').update(apple); + * $('fruits').innerHTML; + * // -> 'I am a fruit and my name is "apple".' **/ update: (function(){ @@ -445,8 +529,8 @@ Element.Methods = { * Element.insert(@element, content) -> Element * - content (String | Element | Object): The content to insert. * - * Inserts content `above`, `below`, at the `top`, and/or at the `bottom` of the - * given element, depending on the option(s) given. + * Inserts content `above`, `below`, at the `top`, and/or at the `bottom` of + * the given element, depending on the option(s) given. * * `insert` accepts content in any of these forms: * - [[String]]: A string of HTML to be parsed and rendered @@ -455,9 +539,9 @@ Element.Methods = { * - ...any object with a `toHTML` method: The method is called and the resulting HTML string * is parsed and rendered * - * The `content` argument can be the content to insert, in which case the implied - * insertion point is `bottom`, or an object that specifies one or more insertion - * points (e.g., `{ bottom: "foo", top: "bar" }`). + * The `content` argument can be the content to insert, in which case the + * implied insertion point is `bottom`, or an object that specifies one or + * more insertion points (e.g., `{ bottom: "foo", top: "bar" }`). * * Accepted insertion points are: * - `before` (as `element`'s previous sibling) @@ -465,6 +549,10 @@ Element.Methods = { * - `top` (as `element`'s first child) * - `bottom` (as `element`'s last child) * + * Note that if the inserted HTML contains any ` **/ Form.Observer = Class.create(Abstract.TimedObserver, { /** From fb25ff28bde73f6bf078b96326529f9ebf9468f1 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Tue, 2 Feb 2010 22:16:40 -0800 Subject: [PATCH 137/502] doc: Merge/update old Template docs into source [#133 state:fixed_in_branch] --- src/lang/template.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lang/template.js b/src/lang/template.js index f9773f566..690fbd78b 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -14,7 +14,7 @@ * expression. The `Template` class provides a much nicer and clearer way of * achieving this formatting. * - *
    Straightforward templates
    + * ##### Straightforward templates * * The `Template` class uses a basic formatting syntax, similar to what is * used in Ruby. The templates are created from strings that have embedded @@ -36,7 +36,7 @@ * myTemplate.evaluate(show); * // -> "The TV show The Simpsons was created by Matt Groening." * - *
    Templates are meant to be reused
    + * ##### Templates are meant to be reused * * As the example illustrates, `Template` objects are not tied to specific * data. The data is bound to the template only during the evaluation of the @@ -60,7 +60,7 @@ * // -> Multiply by 0.9478 to convert from kilojoules to BTUs. * // -> Multiply by 1024 to convert from megabytes to gigabytes. * - *
    Escape sequence
    + * ##### Escape sequence * * There's always the chance that one day you'll need to have a literal in your * template that looks like a symbol, but is not supposed to be replaced. For @@ -75,7 +75,7 @@ * t.evaluate(data); * // -> in Ruby we also use the #{variable} syntax for templates. * - *
    Custom syntaxes
    + * ##### Custom syntaxes * * The default syntax of the template strings will probably be enough for most * scenarios. In the rare occasion where the default Ruby-like syntax is @@ -122,6 +122,23 @@ var Template = Class.create({ * * Applies the template to `object`'s data, producing a formatted string * with symbols replaced by `object`'s corresponding properties. + * + * ##### Examples + * + * var hrefTemplate = new Template('/dir/showAll?lang=#{language}&categ=#{category}&lv=#{levels}'); + * var selection = {category: 'books' , language: 'en-US'}; + * + * hrefTemplate.evaluate(selection); + * // -> '/dir/showAll?lang=en-US&categ=books&lv=' + * + * hrefTemplate.evaluate({language: 'jp', levels: 3, created: '10/12/2005'}); + * // -> '/dir/showAll?lang=jp&categ=&lv=3' + * + * hrefTemplate.evaluate({}); + * // -> '/dir/showAll?lang=&categ=&lv=' + * + * hrefTemplate.evaluate(null); + * // -> error ! **/ evaluate: function(object) { if (object && Object.isFunction(object.toTemplateReplacements)) From bb83058ceab763c4a4338ca3577166d118345c66 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Mon, 1 Feb 2010 20:21:22 -0800 Subject: [PATCH 138/502] doc: Merge/update old Event.stop docs into source [#96 state:fixed_in_branch] --- src/dom/event.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/dom/event.js b/src/dom/event.js index d1ebd6ad2..5f933c8d8 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -221,7 +221,41 @@ * * Stopping an event also sets a `stopped` property on that event for * future inspection. - **/ + * + * There are two aspects to how your browser handles an event once it fires up: + * + * 1. The browser usually triggers event handlers on the actual element the + * event occurred on, then on its parent element, and so on and so forth, + * until the document's root element is reached. This is called + * *event bubbling*, and is the most common form of event propagation. You + * may very well want to stop this propagation when you just handled an event, + * and don't want it to keep bubbling up (or see no need for it). + * + * 2. Once your code had a chance to process the event, the browser handles + * it as well, if that event has a *default behavior*. For instance, clicking + * on links navigates to them; submitting forms sends them over to the server + * side; hitting the Return key in a single-line form field submits it; etc. + * You may very well want to prevent this default behavior if you do your own + * handling. + * + * Because stopping one of those aspects means, in 99.9% of the cases, + * preventing the other one as well, Prototype bundles both in this `stop` + * function. Calling it on an event object, stops propagation *and* prevents + * the default behavior. + * + * ##### Example + * + * Here's a simple script that prevents a form from being sent to the server + * side if certain field is empty. + * + * Event.observe('signinForm', 'submit', function(event) { + * var login = $F('login').strip(); + * if ('' == login) { + * Event.stop(event); + * // Display the issue one way or another + * } + * }); + **/ function stop(event) { Event.extend(event); event.preventDefault(); From 427ea2902f31b560e42a550b1e3d8e6dc5f2ffa9 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Thu, 28 Jan 2010 20:37:48 -0800 Subject: [PATCH 139/502] doc: Merge/update old Form.reset docs into source [#105 state:fixed_in_branch] --- src/dom/form.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/dom/form.js b/src/dom/form.js index b889b3cf1..6ed9e6732 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -13,9 +13,21 @@ var Form = { /** - * Form.reset(form) -> Element + * Form.reset(@form) -> Element * * Resets a form to its default values. + * + * Example usage: + * + * Form.reset('contact') + * + * // equivalent: + * $('contact').reset() + * + * // both have the same effect as pressing the reset button + * + * This method allows you to programatically reset a form. It is a wrapper + * for the `reset()` method native to `HTMLFormElement`. **/ reset: function(form) { form = $(form); From 53d00c7ecc95e4fc7bbe1556a750523ec3f2568b Mon Sep 17 00:00:00 2001 From: dandean Date: Wed, 20 Jan 2010 09:48:20 -0800 Subject: [PATCH 140/502] doc: Merge/update old Object.keys and Object.values docs into source [#119 state:fixed_in_branch] --- src/lang/object.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index e791d603a..219c61ba4 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -132,9 +132,17 @@ * Returns an array of the object's property names. * * Note that the order of the resulting array is browser-dependent — it - * relies on the `for…in` loop, for which the ECMAScript spec does not + * relies on the `for...in` loop, for which the ECMAScript spec does not * prescribe an enumeration order. Sort the resulting array if you wish to * normalize the order of the object keys. + * + * ##### Examples + * + * Object.keys(); + * // -> [] + * + * Object.keys({ name: 'Prototype', version: '1.6.1' }).sort(); + * // -> ['name', 'version'] **/ function keys(object) { var results = []; @@ -147,14 +155,22 @@ * Object.values(object) -> Array * - object (Object): The object to pull values from. * - * Returns an array of the object's values. + * Returns an array of the object's property values. * * Note that the order of the resulting array is browser-dependent — it - * relies on the `for…in` loop, for which the ECMAScript spec does not + * relies on the `for...in` loop, for which the ECMAScript spec does not * prescribe an enumeration order. * * Also, remember that while property _names_ are unique, property _values_ * have no such constraint. + * + * ##### Examples + * + * Object.values(); + * // -> [] + * + * Object.values({ name: 'Prototype', version: '1.6.1' }).sort(); + * // -> ['1.6.1', 'Prototype'] **/ function values(object) { var results = []; From 19cb15806506c806fb38a36870d5baaf889d4b0d Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Mon, 8 Feb 2010 22:24:39 -0800 Subject: [PATCH 141/502] doc: Merge/update old String query-related docs into source [#129 state:fixed_in_branch] --- src/lang/string.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/lang/string.js b/src/lang/string.js index 35ef7b753..206457fad 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -359,6 +359,40 @@ Object.extend(String.prototype, (function() { * * Parses a URI-like query string and returns an object composed of * parameter/value pairs. + * + * This method is realy targeted at parsing query strings (hence the default + * value of`"&"` for the `separator` argument). + * + * For this reason, it does _not_ consider anything that is either before a + * question mark (which signals the beginning of a query string) or beyond + * the hash symbol (`"#"`), and runs `decodeURIComponent()` on each + * parameter/value pair. + * + * `String#toQueryParams` also aggregates the values of identical keys into + * an array of values. + * + * Note that parameters which do not have a specified value will be set to + * `undefined`. + * + * ##### Examples + * + * 'section=blog&id=45'.toQueryParams(); + * // -> {section: 'blog', id: '45'} + * + * 'section=blog;id=45'.toQueryParams(); + * // -> {section: 'blog', id: '45'} + * + * 'http://www.example.com?section=blog&id=45#comments'.toQueryParams(); + * // -> {section: 'blog', id: '45'} + * + * 'section=blog&tag=javascript&tag=prototype&tag=doc'.toQueryParams(); + * // -> {section: 'blog', tag: ['javascript', 'prototype', 'doc']} + * + * 'tag=ruby%20on%20rails'.toQueryParams(); + * // -> {tag: 'ruby on rails'} + * + * 'id=45&raw'.toQueryParams(); + * // -> {id: '45', raw: undefined} **/ function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); From 6634be5a83057557ec75ef9fa344a0acaefd97e4 Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 9 Feb 2010 23:35:27 -0800 Subject: [PATCH 142/502] doc: Merge/update old Event.element/findElement docs into source [#94 state:fixed_in_branch] --- src/dom/event.js | 52 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 5f933c8d8..705a7dee7 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -114,9 +114,38 @@ /** deprecated * Event.element(@event) -> Element + * - event (Event): An Event object * * Returns the DOM element on which the event occurred. This method - * is deprecated, use [[Event.findElement findElement]] instead. + * is deprecated, use [[Event.findElement]] instead. + * + * ##### Example + * + * Here's a simple bit of code which hides any paragraph when directly clicked. + * + * document.observe('click', function(event) { + * var element = Event.element(event); + * if ('P' == element.tagName) + * element.hide(); + * }); + * + * ##### See also + * + * There is a subtle distinction between this function and + * [[Event.findElement]]. + * + * ##### Note for Prototype 1.5.0 + * + * Note that prior to version 1.5.1, if the browser does not support *native DOM extensions* + * (see the [[Element]] section for further details), the element returned by + * [[Event.element]] might very well *not be extended*. If you intend to use + * methods from [[Element.Methods]] on it, you need to wrap the call in the + * [[$]] function like so: + * + * document.observe('click', function(event) { + * var element = $(Event.element(event)); + * // ... + * }); **/ function element(event) { event = Event.extend(event); @@ -143,11 +172,28 @@ } /** - * Event.findElement(@event, expression) -> Element + * Event.findElement(@event[, expression]) -> Element + * - event (Event): An Event object + * - expression (String): An optional CSS selector * * Returns the first DOM element that matches a given CSS selector — * starting with the element on which the event occurred, then moving up - * its ancestor chain. + * its ancestor chain. If `expression` is not given, the element which fired + * the event is returned. + * + * *If no matching element is found, the document itself (`HTMLDocument` node) + * is returned.* + * + * ##### Example + * + * Here's a simple code that lets you click everywhere on the page and hides + * the closest-fitting paragraph around your click (if any). + * + * document.observe('click', function(event) { + * var element = Event.findElement(event, 'p'); + * if (element != document) + * $(element).hide(); + * }); **/ function findElement(event, expression) { var element = Event.element(event); From 5cb5ee489e5ea789521a6ded9f9d74004281503c Mon Sep 17 00:00:00 2001 From: Thomas Fuchs Date: Wed, 10 Feb 2010 14:03:12 +0100 Subject: [PATCH 143/502] Also detect embedded (UIWebView) mobile Safari. --- CHANGELOG | 2 ++ src/prototype.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 326ba74fe..91c7eda74 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Also detect embedded (UIWebView) mobile Safari. (Thomas Fuchs) + * Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. Replace with feature test and a simple boolean check at runtime. (kangax) * Optimize Element#immediateDescendants. (kangax, Tobie Langel) diff --git a/src/prototype.js b/src/prototype.js index 5f78f7824..78b5f2981 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -19,7 +19,7 @@ var Prototype = { Opera: isOpera, WebKit: ua.indexOf('AppleWebKit/') > -1, Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile.*Safari/.test(ua) + MobileSafari: /Apple.*Mobile/.test(ua) } })(), From 37d5c5eb1bb498f20a4769e8a99176ca096964a0 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 15 Feb 2010 07:58:19 -0800 Subject: [PATCH 144/502] doc: Merge/update old docs for Form.findFirstElement and focusFirstelement into source [#102 state:fixed_in_branch] --- src/dom/form.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/dom/form.js b/src/dom/form.js index 6ed9e6732..e0f5a9ceb 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -218,6 +218,15 @@ Form.Methods = { * Form.findFirstElement(@form) -> Element * * Finds the first non-hidden, non-disabled control within the form. + * + * The returned object is either an INPUT, SELECT or TEXTAREA element. This + * method is used by the [[Form.focusFirstElement]] method. + * + * ##### Note + * + * The result of this method is the element that comes first in the + * *document* order, not the + * [tabindex order](http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1). **/ findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { @@ -236,6 +245,11 @@ Form.Methods = { * Form.focusFirstElement(@form) -> Element * * Gives keyboard focus to the first element of the form. Returns the form. + * + * Uses [[Form.findFirstElement]] to get the first element and calls + * [[Form.Element.activate]] on it. This is useful for enhancing usability on + * your site by bringing focus on page load to forms such as search forms or + * contact forms where a user is ready to start typing right away. **/ focusFirstElement: function(form) { form = $(form); From 9fad02d007b232fea7b4cf924c53463d2e4f6a10 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 15 Feb 2010 07:43:12 -0800 Subject: [PATCH 145/502] doc: Merge/update old Form.getElements and getInputs docs into source [#103 state:fixed_in_branch] --- src/dom/form.js | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index e0f5a9ceb..c999a76a4 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -129,6 +129,11 @@ Form.Methods = { * Form.getElements(@form) -> [Element...] * * Returns a collection of all controls within a form. + * + * ##### Note + * + * OPTION elements are not included in the result; only their parent + * SELECT control is. **/ getElements: function(form) { var elements = $(form).getElementsByTagName('*'), @@ -149,15 +154,31 @@ Form.Methods = { /** * Form.getInputs(@form [, type [, name]]) -> [Element...] - * - type (String): A value for the `type` attribute against which to - * filter. - * - name (String): A value for the `name` attribute against which to - * filter. + * - type (String): A value for the `type` attribute against which to filter. + * - name (String): A value for the `name` attribute against which to filter. * - * Returns a collection of all `INPUT` elements in a form. + * Returns a collection of all INPUT elements in a form. * * Use optional `type` and `name` arguments to restrict the search on * these attributes. + * + * ##### Example + * + * var form = $('myform') + * + * form.getInputs() // -> all INPUT elements + * form.getInputs('text') // -> only text inputs + * + * var buttons = form.getInputs('radio', 'education') + * // -> only radio buttons of name "education" + * + * // now disable these radio buttons: + * buttons.invoke('disable') + * + * ##### Note + * + * Elements are returned in the *document* order, not the + * [tabindex order](http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1). **/ getInputs: function(form, typeName, name) { form = $(form); From 6c18fc888824223e816722a0069500b4053e88f8 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Sun, 14 Feb 2010 16:40:25 -0800 Subject: [PATCH 146/502] doc: Merge/update old Form.request docs into source [#104 state:fixed_in_branch] --- src/dom/form.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index c999a76a4..b22548006 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -280,15 +280,75 @@ Form.Methods = { /** * Form.request(@form[, options]) -> Ajax.Request - * - options (Object): Options to pass along to the `Ajax.Request` + * - options (Object): Options to pass along to the [[Ajax.Request]] * constructor. * * A convenience method for serializing and submitting the form via an * [[Ajax.Request]] to the URL of the form's `action` attribute. * - * The `options` parameter is passed to the `Ajax.Request` instance, + * The `options` parameter is passed to the [[Ajax.Request]] instance, * allowing one to override the HTTP method and/or specify additional - * parameters and callbacks. + * parameters and callbacks. + * + * - If the form has a method attribute, its value is used for the + * [[Ajax.Request]] `method` option. If a method option is passed to + * `request()`, it takes precedence over the form's method attribute. If + * neither is specified, method defaults to "POST". + * + * - Key-value pairs specified in the `parameters` option (either as a hash + * or a query string) will be merged with (and *take precedence* over) the + * serialized form parameters. + * + * ##### Example + * + * Suppose you have this HTML form: + * + *
    + *
    User info + *
      + *
    • + * + * + *
    • + *
    • + * + * + *
    • + *
    • + * + * + *
    • + *
    + * + *
    + *
    + * + * You can easily post it with Ajax like this: + * + * $('person-example').request(); //done - it's posted + * + * // do the same with a callback: + * $('person-example').request({ + * onComplete: function(){ alert('Form data saved!') } + * }) + * + * To override the HTTP method and add some parameters, simply use `method` + * and `parameters` in the options. In this example we set the method to GET + * and set two fixed parameters: + * `interests` and `hobbies`. The latter already exists in the form but this + * value will take precedence. + * + * $('person-example').request({ + * method: 'get', + * parameters: { interests:'JavaScript', 'hobbies[]':['programming', 'music'] }, + * onComplete: function(){ alert('Form data saved!') } + * }) **/ request: function(form, options) { form = $(form), options = Object.clone(options || { }); From bbe9d0ba226180402e0e57a271c90ec1aa066abe Mon Sep 17 00:00:00 2001 From: dandean Date: Sat, 13 Feb 2010 17:47:23 -0800 Subject: [PATCH 147/502] doc: Merge/update the old PeriodicalExecuter docs into source [#122 state:fixed_in_branch] --- src/lang/periodical_executer.js | 35 ++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/lang/periodical_executer.js b/src/lang/periodical_executer.js index c4232621d..aa3b97715 100644 --- a/src/lang/periodical_executer.js +++ b/src/lang/periodical_executer.js @@ -1,24 +1,32 @@ /** section: Language * class PeriodicalExecuter * - * A class that oversees the calling of a particular function periodically. + * Oversees the calling of a particular function periodically. * - * `PeriodicalExecuter` shields you from multiple parallel executions of the + * [[PeriodicalExecuter]] shields you from multiple parallel executions of a * `callback` function, should it take longer than the given interval to * execute. * * This is especially useful if you use one to interact with the user at * given intervals (e.g. use a prompt or confirm call): this will avoid * multiple message boxes all waiting to be actioned. + * + * ##### Example + * + * new PeriodicalExecuter(function(pe) { + * if (!confirm('Want me to annoy you again later?')) { + * pe.stop(); + * } + * }, 5); **/ var PeriodicalExecuter = Class.create({ /** * new PeriodicalExecuter(callback, frequency) * - callback (Function): the function to be executed at each interval. - * - frequency (Number): the amount of time, in sections, to wait in between - * callbacks. + * - frequency (Number): the amount of time, in seconds, to wait in between + * callbacks. * - * Creates an `PeriodicalExecuter`. + * Creates a [[PeriodicalExecuter]]. **/ initialize: function(callback, frequency) { this.callback = callback; @@ -40,6 +48,23 @@ var PeriodicalExecuter = Class.create({ * PeriodicalExecuter#stop() -> undefined * * Stops the periodical executer (there will be no further triggers). + * + * Once a [[PeriodicalExecuter]] is created, it constitues an infinite loop, + * triggering at the given interval until the page unloads. This method lets + * you stop it any time you want. + * + * ##### Example + * + * This will only alert 1, 2 and 3, then the [[PeriodicalExecuter]] stops. + * + * var count = 0; + * new PeriodicalExecuter(function(pe) { + * if (++count > 3) { + * pe.stop(); + * } else { + * alert(count); + * } + * }, 1); **/ stop: function() { if (!this.timer) return; From 6f85bc9a417d814c190594df395e748e72a95930 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Wed, 10 Feb 2010 22:05:44 -0800 Subject: [PATCH 148/502] doc: Merge/update old String scripts-related docs into source [#128 state:fixed_in_branch] --- src/lang/string.js | 50 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/lang/string.js b/src/lang/string.js index 206457fad..10057a06e 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -260,16 +260,17 @@ Object.extend(String.prototype, (function() { * * Strips a string of things that look like an HTML script blocks. * - *
    Example
    + * ##### Example * * "

    This is a test.End of test

    ".stripScripts(); * // => "

    This is a test.End of test

    " * - *
    Caveat User
    + * ##### Caveat User * * Note that the processing `stripScripts` does is good enough for most purposes, - * but you cannot rely on it for security purposes. If you're processing end-user-supplied - * content, `stripScripts` is probably not sufficiently robust to prevent hack attacks. + * but you cannot rely on it for security purposes. If you're processing + * end-user-supplied content, `stripScripts` is probably not sufficiently robust + * to prevent hack attacks. **/ function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); @@ -278,8 +279,33 @@ Object.extend(String.prototype, (function() { /** * String#extractScripts() -> Array * - * Extracts the content of any script blocks present in the string and + * Extracts the content of any `script` blocks present in the string and * returns them as an array of strings. + * + * This method is used internally by [[String#evalScripts]]. + * It does _not_ evaluate the scripts (use [[String#evalScripts]] + * to do that), but can be usefull if you need to evaluate the scripts at a + * later date. + * + * ##### Examples + * + * 'lorem... '.extractScripts(); + * // -> ['2 + 2'] + * + * ''.extractScripts(); + * // -> ['2 + 2', 'alert("hello world!")'] + * + * ##### Notes + * + * To evaluate the scripts later on, you can use the following: + * + * var myScripts = ''.extractScripts(); + * // -> ['2 + 2', 'alert("hello world!")'] + * + * var myReturnedValues = myScripts.map(function(script) { + * return eval(script); + * }); + * // -> [4, undefined] (and displays 'hello world!' in the alert dialog) **/ function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), @@ -297,8 +323,16 @@ Object.extend(String.prototype, (function() { * `'.evalScripts(); + * // -> [4] + * + * ''.evalScripts(); + * // -> [4, undefined] (and displays 'hello world!' in the alert dialog) * - *
    About `evalScripts`, `var`s, and defining functions
    + * ##### About `evalScripts`, `var`s, and defining functions * * `evalScripts` evaluates script blocks, but this **does not** mean they are * evaluated in the global scope. They aren't, they're evaluated in the scope of @@ -324,7 +358,9 @@ Object.extend(String.prototype, (function() { * // Amazing stuff! * } * - * (You can leave off the `window.` part of that, but it's bad form.) + * (You can leave off the `window.` part of that, but it's bad form.) + * Evaluates the content of any `script` block present in the string. Returns an array + * containing the value returned by each script. **/ function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); From 5f612612c605d8c5252a2478e3b3f31e38f56c62 Mon Sep 17 00:00:00 2001 From: dandean Date: Wed, 10 Feb 2010 16:51:17 -0800 Subject: [PATCH 149/502] doc: Merge/update old Event observe/stopObserving docs into source [#95 state:fixed_in_branch] --- src/dom/event.js | 158 ++++++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 72 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 705a7dee7..d06fa48a9 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -480,35 +480,37 @@ /** * Event.observe(element, eventName, handler) -> Element * - element (Element | String): The DOM element to observe, or its ID. - * - eventName (String): The name of the event, in all lower case, without the "on" - * prefix — e.g., "click" (not "onclick"). + * - eventName (String): The name of the event, in all lower case, without + * the "on" prefix — e.g., "click" (not "onclick"). * - handler (Function): The function to call when the event occurs. * * Registers an event handler on a DOM element. Aliased as [[Element#observe]]. * - * `Event.observe` smooths out a variety of differences between browsers and provides - * some handy additional features as well. Key features in brief: + * [[Event.observe]] smooths out a variety of differences between browsers + * and provides some handy additional features as well. Key features in brief: * * Several handlers can be registered for the same event on the same element. * * Prototype figures out whether to use `addEventListener` (W3C standard) or * `attachEvent` (MSIE); you don't have to worry about it. * * The handler is passed an _extended_ [[Event]] object (even on MSIE). - * * The handler's context (`this` value) is set to the extended element being observed - * (even if the event actually occurred on a descendent element and bubbled up). - * * Prototype handles cleaning up the handler when leaving the page (important for MSIE memory - * leak prevention). - * * `observe` makes it possible to stop observing the event easily via [[Event.stopObserving]]. - * * Adds support for `mouseenter` / `mouseleave` in all browsers. - * - * Although you can use `Event.observe` directly and there are times when that's the most - * convenient or direct way, it's more common to use its alias [[Element#observe]]. These two - * statements have the same effect: + * * The handler's context (`this` value) is set to the extended element + * being observed (even if the event actually occurred on a descendent + * element and bubbled up). + * * Prototype handles cleaning up the handler when leaving the page + * (important for MSIE memory leak prevention). + * * [[Event.observe]] makes it possible to stop observing the event easily + * via [[Event.stopObserving]]. + * * Adds support for `mouseenter` / `mouseleave` events in all browsers. + * + * Although you can use [[Event.observe]] directly and there are times when + * that's the most convenient or direct way, it's more common to use its + * alias [[Element#observe]]. These two statements have the same effect: * * Event.observe('foo', 'click', myHandler); * $('foo').observe('click', myHandler); * * The examples in this documentation use the [[Element#observe]] form. * - *
    The Handler
    + * ##### The Handler * * Signature: * @@ -516,35 +518,39 @@ * // `this` = the element being observed * } * - * So for example, this will turn the background of the element 'foo' blue when it's clicked: + * So for example, this will turn the background of the element 'foo' blue + * when it's clicked: * * $('foo').observe('click', function(event) { * this.setStyle({backgroundColor: 'blue'}); * }); * - * Note that we used `this` to refer to the element, and that we received the `event` object - * as a parameter (even on MSIE). + * Note that we used `this` to refer to the element, and that we received the + * `event` object as a parameter (even on MSIE). * - *
    It's All About Timing
    + * ##### It's All About Timing * - * One of the most common errors trying to observe events is trying to do it before the element - * exists in the DOM. Don't try to observe elements until after the - * [[document.observe dom:loaded]] event or `window` `load` event has been fired. + * One of the most common errors trying to observe events is trying to do it + * before the element exists in the DOM. Don't try to observe elements until + * after the [[document.observe dom:loaded]] event or `window` `load` event + * has been fired. * - *
    Preventing the Default Event Action and Bubbling
    + * ##### Preventing the Default Event Action and Bubbling * - * If we want to stop the event (e.g., prevent its default action and stop it bubbling), we can - * do so with the extended event object's [[Event#stop]] method: + * If we want to stop the event (e.g., prevent its default action and stop it + * bubbling), we can do so with the extended event object's [[Event#stop]] + * method: * * $('foo').observe('click', function(event) { * event.stop(); * }); * - *
    Finding the Element Where the Event Occurred
    + * ##### Finding the Element Where the Event Occurred * - * Since most events bubble from descendant elements up through the hierarchy until they're - * handled, we can observe an event on a container rather than individual elements within the - * container. This is sometimes called "event delegation". It's particularly handy for tables: + * Since most events bubble from descendant elements up through the hierarchy + * until they're handled, we can observe an event on a container rather than + * individual elements within the container. This is sometimes called "event + * delegation". It's particularly handy for tables: * * * @@ -560,27 +566,27 @@ * Instead of observing each cell or row, we can simply observe the table: * * $('records').observe('click', function(event) { - * var clickedRow; - * clickedRow = event.findElement('tr'); + * var clickedRow = event.findElement('tr'); * if (clickedRow) { * this.down('th').update("You clicked record #" + clickedRow.readAttribute("data-recnum")); * } * }); * - * When any row in the table is clicked, we update the table's first header cell saying which - * record was clicked. [[Event#findElement]] finds the row that was clicked, and `this` refers - * to the table we were observing. + * When any row in the table is clicked, we update the table's first header + * cell saying which record was clicked. [[Event#findElement]] finds the row + * that was clicked, and `this` refers to the table we were observing. * - *
    Stopping Observing the Event
    + * ##### Stopping Observing the Event * - * If we don't need to observe the event anymore, we can stop observing it with - * [[Event.stopObserving]] (aka [[Element#stopObserving]]). + * If we don't need to observe the event anymore, we can stop observing it + * with [[Event.stopObserving]] or its [[Element#stopObserving]] alias. * - *
    Using an Instance Method as a Handler
    + * ##### Using an Instance Method as a Handler * - * If we want to use an instance method as a handler, we will probably want to use - * [[Function#bind]] to set the handler's context; otherwise, the context will be lost and - * `this` won't mean what we expect it to mean within the handler function. E.g.: + * If we want to use an instance method as a handler, we will probably want + * to use [[Function#bind]] to set the handler's context; otherwise, the + * context will be lost and `this` won't mean what we expect it to mean + * within the handler function. E.g.: * * var MyClass = Class.create({ * initialize: function(name, element) { @@ -595,28 +601,31 @@ * }, * }); * - * Without the `bind`, when `handleClick` was triggered by the event, `this` wouldn't - * refer to the instance and so the alert wouldn't show the name. Because we used `bind`, it - * works correctly. See [[Function#bind]] for - * details. There's also [[Function#bindAsEventListener]], which is handy for certain very - * specific situations. (Normally, `bind` is all you need.) + * Without the [[Function#bind]], when `handleClick` was triggered by the + * event, `this` wouldn't refer to the instance and so the alert wouldn't + * show the name. Because we used [[Function#bind]], it works correctly. See + * [[Function#bind]] for details. There's also [[Function#bindAsEventListener]], + * which is handy for certain very specific situations. (Normally, + * [[Function#bind]] is all you need.) * - *
    Side Notes
    + * ##### Side Notes * - * Although Prototype smooths out most of the differences between browsers, the fundamental - * behavior of a browser implementation isn't changed. For example, the timing of the `change` - * or `blur` events varies a bit from browser to browser. + * Although Prototype smooths out most of the differences between browsers, + * the fundamental behavior of a browser implementation isn't changed. For + * example, the timing of the `change` or `blur` events varies a bit from + * browser to browser. * - *
    Changes in 1.6.x
    + * ##### Changes in 1.6.x * - * Prior to Prototype 1.6, `observe` supported a fourth argument (`useCapture`), a boolean that - * indicated whether to use the browser's capturing phase or its bubbling phase. Since MSIE does - * not support the capturing phase, we removed this argument from 1.6, lest it give users the + * Prior to Prototype 1.6, [[Event.observe]] supported a fourth argument + * (`useCapture`), a boolean that indicated whether to use the browser's + * capturing phase or its bubbling phase. Since MSIE does not support the + * capturing phase, we removed this argument from 1.6, lest it give users the * false impression that they can use the capturing phase in all browsers. * - * 1.6 also introduced setting the `this` context to the element being observed, automatically - * extending the [[Event]] object, and the [[Event#findElement]] method. - * + * 1.6 also introduced setting the `this` context to the element being + * observed, automatically extending the [[Event]] object, and the + * [[Event#findElement]] method. **/ function observe(element, eventName, handler) { element = $(element); @@ -651,18 +660,20 @@ /** * Event.stopObserving(element[, eventName[, handler]]) -> Element * - element (Element | String): The element to stop observing, or its ID. - * - eventName (String): _(Optional)_ The name of the event to stop observing, in all lower case, - * without the "on" — e.g., "click" (not "onclick"). - * - handler (Function): _(Optional)_ The handler to remove; must be the _exact same_ reference - * that was passed to [[Event.observe]] (see below.). + * - eventName (String): _(Optional)_ The name of the event to stop + * observing, in all lower case, without the "on" — e.g., + * "click" (not "onclick"). + * - handler (Function): _(Optional)_ The handler to remove; must be the + * _exact same_ reference that was passed to [[Event.observe]]. * * Unregisters one or more event handlers. * * If `handler` is omitted, unregisters all event handlers on `element` * for that `eventName`. If `eventName` is also omitted, unregisters _all_ - * event handlers on `element`. (In each case, only affects handlers registered via Prototype.) + * event handlers on `element`. (In each case, only affects handlers + * registered via Prototype.) * - *
    Examples
    + * ##### Examples * * Assuming: * @@ -672,18 +683,21 @@ * * $('foo').stopObserving('click', myHandler); * - * If we want to remove _all_ 'click' handlers from 'foo', we leave off the handler argument: + * If we want to remove _all_ 'click' handlers from 'foo', we leave off the + * handler argument: * * $('foo').stopObserving('click'); * - * If we want to remove _all_ handlers for _all_ events from 'foo' (perhaps we're about to remove - * it from the DOM), we simply omit both the handler and the event name: + * If we want to remove _all_ handlers for _all_ events from 'foo' (perhaps + * we're about to remove it from the DOM), we simply omit both the handler + * and the event name: * * $('foo').stopObserving(); * - *
    A Common Error
    + * ##### A Common Error * - * When using instance methods as observers, it's common to use [[Function#bind]] on them, e.g.: + * When using instance methods as observers, it's common to use + * [[Function#bind]] on them, e.g.: * * $('foo').observe('click', this.handlerMethod.bind(this)); * @@ -691,9 +705,10 @@ * * $('foo').stopObserving('click', this.handlerMethod.bind(this)); // <== WRONG * - * [[Function#bind]] returns a _new_ function every time it's called, and so if you don't retain - * the reference you used when observing, you can't unhook that function specifically. (You can - * still unhook __all__ handlers for an event, or all handlers on the element entirely.) + * [[Function#bind]] returns a _new_ function every time it's called, and so + * if you don't retain the reference you used when observing, you can't + * unhook that function specifically. (You can still unhook __all__ handlers + * for an event, or all handlers on the element entirely.) * * To do this, you need to keep a reference to the bound function: * @@ -703,7 +718,6 @@ * ...and then to remove: * * $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right - * **/ function stopObserving(element, eventName, handler) { element = $(element); From 247b3327012876915946ad81336ddcafaa671a29 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Thu, 11 Feb 2010 20:21:31 -0800 Subject: [PATCH 150/502] doc: Merge/update Object overview docs into source [#115 state:fixed_in_branch] --- src/lang/object.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 219c61ba4..36430216a 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -1,12 +1,23 @@ /** section: Language * class Object * - * Extensions to the built-in `Object` object. + * Extensions to the built-in [[Object]] object. * * Because it is dangerous and invasive to augment `Object.prototype` (i.e., * add instance methods to objects), all these methods are static methods that - * take an `Object` as their first parameter. + * take an [[Object]] as their first parameter. * + * [[Object]] is used by Prototype as a namespace; that is, it just keeps a few + * new methods together, which are intended for namespaced access (i.e. starting + * with "`Object.`"). + * + * For the regular developer (who simply uses Prototype without tweaking it), the + * most commonly used methods are probably [[Object.inspect]] and, to a lesser degree, + * [[Object.clone]]. + * + * Advanced users, who wish to create their own objects like Prototype does, or + * explore objects as if they were hashes, will turn to [[Object.extend]], + * [[Object.keys]], and [[Object.values]]. **/ (function() { From d43c6d453f819b774978375e704c106cb7df2e86 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Feb 2010 14:06:42 -0600 Subject: [PATCH 151/502] Fix `Element.getDimensions`. --- src/dom/layout.js | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 927fad5e2..29a982764 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -1,5 +1,4 @@ - (function() { // Converts a CSS percentage value to a decimal. @@ -23,7 +22,7 @@ return null; } - // Non-IE browsers will always return pixels. + // Non-IE browsers will always return pixels if possible. if ((/^\d+(px)?$/i).test(value)) { return window.parseInt(value, 10); } @@ -60,6 +59,7 @@ return 0; } + // Turns plain numbers into pixel measurements. function toCSSPixels(number) { if (Object.isString(number) && number.endsWith('px')) { return number; @@ -75,12 +75,11 @@ return false; } element = $(element.parentNode); - } + } return true; } - var hasLayout = Prototype.K; - + var hasLayout = Prototype.K; if ('currentStyle' in document.documentElement) { hasLayout = function(element) { if (!element.currentStyle.hasLayout) { @@ -97,7 +96,6 @@ return key; } - /** * class Element.Layout < Hash * @@ -237,8 +235,7 @@ _set: function(property, value) { return Hash.prototype.set.call(this, property, value); - }, - + }, // TODO: Investigate. set: function(property, value) { @@ -315,7 +312,6 @@ // its parent. var parent = element.parentNode, pLayout = $(parent).getLayout(); - newWidth = pLayout.get('width') - this.get('margin-left') - this.get('border-left') - @@ -352,21 +348,24 @@ /** * Element.Layout#toCSS([keys...]) -> Object - * + * - keys (String): A space-separated list of keys to include. + * * Converts the layout hash to a plain object of CSS property/value * pairs, optionally including only the given keys. + * + * Keys can be passed into this method as individual arguments _or_ + * separated by spaces within a string. + * + * // Equivalent statements: + * someLayout.toCSS('top', 'bottom', 'left', 'right'); + * someLayout.toCSS('top bottom left right'); * * Useful for passing layout properties to [[Element.setStyle]]. **/ toCSS: function() { - var keys = []; - for (var i = 0, j, argKeys, l = arguments.length; i < l; i++) { - argKeys = arguments[i].split(' '); - for (j = 0; j < argKeys.length; j++) { - keys.push(argKeys[j]); - } - } - if (keys.length === 0) keys = Element.Layout.PROPERTIES; + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); var css = {}; keys.each( function(key) { // Key needs to be a valid Element.Layout property... @@ -380,11 +379,14 @@ if (value) css[cssNameFor(key)] = value + 'px'; }); return css; + }, + + inspect: function() { + return "#"; } }); Object.extend(Element.Layout, { - // All measurable properties. /** * Element.Layout.PROPERTIES = Array * @@ -594,6 +596,9 @@ * * A representation of the top- and left-offsets of an element relative to * another. + * + * All methods that compute offsets return an instance of `Element.Offset`. + * **/ Element.Offset = Class.create({ /** @@ -689,8 +694,8 @@ function getDimensions(element) { var layout = $(element).getLayout(); return { - width: layout.measure('width'), - height: layout.measure('height') + width: layout.get('width'), + height: layout.get('height') }; } From dcdb5ebcb6872920ac320451149b427343fc5a86 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 16 Feb 2010 14:00:33 -0600 Subject: [PATCH 152/502] Ensure `Element#absolutize` returns the element. --- src/dom/layout.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index cf631652e..00b3d3f3c 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -118,15 +118,15 @@ * Instantiate an `Element.Layout` class by passing an element into the * constructor: * - * var layout = new Element.Layout(someElement); + * var layout = new Element.Layout(someElement); * * You can also use [[Element.getLayout]], if you prefer. * * Once you have a layout object, retrieve properties using [[Hash]]'s * familiar `get` and `set` syntax. * - * layout.get('width'); //-> 400 - * layout.get('top'); //-> 180 + * layout.get('width'); //-> 400 + * layout.get('top'); //-> 180 * * The following are the CSS-related properties that can be retrieved. * Nearly all of them map directly to their property names in CSS. (The @@ -184,7 +184,7 @@ * becomes stale when the element's dimensions change**. When this * happens, obtain a new instance. * - *

    Hidden elements

    + *

    Hidden elements

    * * Because it's a common case to want the dimensions of a hidden element * (e.g., for animations), it's possible to measure elements that are @@ -842,7 +842,9 @@ left: offset.left + 'px', width: layout.get('width') + 'px', height: layout.get('height') + 'px' - }); + }); + + return element; } /** From 1b1361269f9e11aca00ba0b6fd5caa6e4c4091f9 Mon Sep 17 00:00:00 2001 From: dandean Date: Mon, 15 Feb 2010 15:58:08 -0800 Subject: [PATCH 153/502] doc: Merge/update old $ docs into the source [#111 state:fixed_in_branch] --- src/dom/dom.js | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3bef48164..339855582 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -7,11 +7,54 @@ * If provided with a string, returns the element in the document with * matching ID; otherwise returns the passed element. * - * Takes in an arbitrary number of arguments. Returns one `Element` if given - * one argument; otherwise returns an array of `Element`s. + * Takes in an arbitrary number of arguments. Returns one [[Element]] if + * given one argument; otherwise returns an [[Array]] of [[Element]]s. * - * All elements returned by the function are "extended" with `Element` + * All elements returned by the function are "extended" with [[Element]] * instance methods. + * + * ##### More Information + * + * The [[$]] function is the cornerstone of Prototype. Not only does it + * provide a handy alias for `document.getElementById`, it also lets you pass + * indifferently IDs (strings) or DOM node references to your functions: + * + * function foo(element) { + * element = $(element); + * // rest of the function... + * } + * + * Code written this way is flexible — you can pass it the ID of the element + * or the element itself without any type sniffing. + * + * Invoking it with only one argument returns the [[Element]], while invoking it + * with multiple arguments returns an [[Array]] of [[Element]]s (and this + * works recursively: if you're twisted, you could pass it an array + * containing some arrays, and so forth). As this is dependent on + * `getElementById`, [W3C specs](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-getElBId) + * apply: nonexistent IDs will yield `null` and IDs present multiple times in + * the DOM will yield erratic results. *If you're assigning the same ID to + * multiple elements, you're doing it wrong!* + * + * The function also *extends every returned element* with [[Element.extend]] + * so you can use Prototype's DOM extensions on it. In the following code, + * the two lines are equivalent. However, the second one feels significantly + * more object-oriented: + * + * // Note quite OOP-like... + * Element.hide('itemId'); + * // A cleaner feel, thanks to guaranted extension + * $('itemId').hide(); + * + * However, when using iterators, leveraging the [[$]] function makes for + * more elegant, more concise, and also more efficient code: + * + * ['item1', 'item2', 'item3'].each(Element.hide); + * // The better way: + * $('item1', 'item2', 'item3').invoke('hide'); + * + * See [How Prototype extends the DOM](http://prototypejs.org/learn/extensions) + * for more info. **/ function $(element) { From 798fd0745aa64e4e5ab595346932696ecec8e3ae Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 16 Feb 2010 07:52:50 -0800 Subject: [PATCH 154/502] doc: Merge/update old String JSON docs into source [#127 state:fixed_in_branch] --- src/lang/string.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/lang/string.js b/src/lang/string.js index 10057a06e..277d648dc 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -600,6 +600,11 @@ Object.extend(String.prototype, (function() { * String#toJSON() -> String * * Returns a JSON string. + * + * ##### Example + * + * 'The "Quoted" chronicles'.toJSON(); + * //-> '"The \"Quoted\" chronicles"' **/ function toJSON() { return this.inspect(true); @@ -610,6 +615,11 @@ Object.extend(String.prototype, (function() { * * Strips comment delimiters around Ajax JSON or JavaScript responses. * This security method is called internally. + * + * ##### Example + * + * '/*-secure-\n{"name": "Violet", "occupation": "character", "age": 25}\n*\/'.unfilterJSON() + * // -> '{"name": "Violet", "occupation": "character", "age": 25}' **/ function unfilterJSON(filter) { return this.replace(filter || Prototype.JSONFilter, '$1'); @@ -620,6 +630,17 @@ Object.extend(String.prototype, (function() { * * Check if the string is valid JSON by the use of regular expressions. * This security method is called internally. + * + * ##### Examples + * + * "something".isJSON(); + * // -> false + * "\"something\"".isJSON(); + * // -> true + * "{ foo: 42 }".isJSON(); + * // -> false + * "{ \"foo\": 42 }".isJSON(); + * // -> true **/ function isJSON() { var str = this; @@ -636,6 +657,33 @@ Object.extend(String.prototype, (function() { * If the optional `sanitize` parameter is set to `true`, the string is * checked for possible malicious attempts; if one is detected, `eval` * is _not called_. + * + * ##### Warning + * + * If the JSON string is not well formated or if a malicious attempt is + * detected a `SyntaxError` is thrown. + * + * ##### Examples + * + * var person = '{ "name": "Violet", "occupation": "character" }'.evalJSON(); + * person.name; + * //-> "Violet" + * + * person = 'grabUserPassword()'.evalJSON(true); + * //-> SyntaxError: Badly formed JSON string: 'grabUserPassword()' + * + * person = '/*-secure-\n{"name": "Violet", "occupation": "character"}\n*\/'.evalJSON() + * person.name; + * //-> "Violet" + * + * ##### Note + * + * Always set the `sanitize` parameter to `true` for data coming from + * externals sources to prevent XSS attacks. + * + * As [[String#evalJSON]] internally calls [[String#unfilterJSON]], optional + * security comment delimiters (defined in [[Prototype.JSONFilter]]) are + * automatically removed. **/ function evalJSON(sanitize) { var json = this.unfilterJSON(); From 05d31eab607a3916962a5ee416c0afc5eb20d41a Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 16 Feb 2010 16:11:02 -0800 Subject: [PATCH 155/502] doc: Merge/update old Object.toXYZ docs into source [#120 state:fixed_in_branch] --- src/lang/object.js | 61 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 36430216a..5719e09a5 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -73,9 +73,18 @@ * `undefined` and `function` types have no JSON representation. `boolean` * and `null` are coerced to strings. * - * For other types, `Object.toJSON` looks for a `toJSON` method on `object`. + * For other types, [[Object.toJSON]] looks for a `toJSON` method on `object`. * If there is one, it is used; otherwise the object is treated like a - * generic `Object`. + * generic [[Object]]. + * + * For more information on Prototype's JSON encoder, hop to our + * [tutorial](http://prototypejs.org/learn/json). + * + * ##### Example + * + * var data = {name: 'Violet', occupation: 'character', age: 25, pets: ['frog', 'rabbit']}; + * Object.toJSON(data); + * //-> '{"name": "Violet", "occupation": "character", "age": 25, "pets": ["frog","rabbit"]}' **/ function toJSON(object) { var type = typeof object; @@ -102,12 +111,12 @@ /** * Object.toQueryString(object) -> String - * object (Object): The object whose property/value pairs will be converted. + * - object (Object): The object whose property/value pairs will be converted. * * Turns an object into its URL-encoded query string representation. * * This is a form of serialization, and is mostly useful to provide complex - * parameter sets for stuff such as objects in the Ajax namespace (e.g. + * parameter sets for stuff such as objects in the [[Ajax]] namespace (e.g. * [[Ajax.Request]]). * * Undefined-value pairs will be serialized as if empty-valued. Array-valued @@ -118,6 +127,11 @@ * The order of pairs in the serialized form is not guaranteed (and mostly * irrelevant anyway) — except for array-based parts, which are serialized * in array order. + * + * ##### Examples + * + * Object.toQueryString({ action: 'ship', order_id: 123, fees: ['f1', 'f2'], 'label': 'a demo' }) + * // -> 'action=ship&order_id=123&fees=f1&fees=f2&label=a%20demo' **/ function toQueryString(object) { return $H(object).toQueryString(); @@ -131,6 +145,45 @@ * * Returns the return value of `object`'s `toHTML` method if it exists; else * runs `object` through [[String.interpret]]. + * + * ##### Examples + * + * var Bookmark = Class.create({ + * initialize: function(name, url) { + * this.name = name; + * this.url = url; + * }, + * + * toHTML: function() { + * return '#{name}'.interpolate(this); + * } + * }); + * + * var api = new Bookmark('Prototype API', 'http://prototypejs.org/api'); + * + * Object.toHTML(api); + * //-> 'Prototype API' + * + * Object.toHTML("Hello world!"); + * //-> "Hello world!" + * + * Object.toHTML(); + * //-> "" + * + * Object.toHTML(null); + * //-> "" + * + * Object.toHTML(undefined); + * //-> "" + * + * Object.toHTML(true); + * //-> "true" + * + * Object.toHTML(false); + * //-> "false" + * + * Object.toHTML(123); + * //-> "123" **/ function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); From 6553701c5899d01256a4116905acf567067ba8ee Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Wed, 17 Feb 2010 02:22:39 +0100 Subject: [PATCH 156/502] doc: Merge/update old String docs into source [#124 state:fixed_in_branch] --- src/lang/string.js | 50 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/lang/string.js b/src/lang/string.js index 277d648dc..cf5bdec0e 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -7,7 +7,7 @@ * ranging from the trivial to the complex. Tired of stripping trailing * whitespace? Try [[String#strip]]. Want to replace `replace`? Have a look at * [[String#sub]] and [[String#gsub]]. Need to parse a query string? We have - * [[String#toQueryParams]]. + * [[String#toQueryParams what you need]]. **/ Object.extend(String, { /** @@ -584,6 +584,18 @@ Object.extend(String.prototype, (function() { * * Returns a debug-oriented version of the string (i.e. wrapped in single or * double quotes, with backslashes and quotes escaped). + * + * For more information on `inspect` methods, see [[Object.inspect]]. + * + * #### Examples + * + * 'I\'m so happy.'.inspect(); + * // -> '\'I\\\'m so happy.\'' + * // (displayed as 'I\'m so happy.' in an alert dialog or the console) + * + * 'I\'m so happy.'.inspect(true); + * // -> '"I'm so happy."' + * // (displayed as "I'm so happy." in an alert dialog or the console) **/ function inspect(useDoubleQuotes) { var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { @@ -697,6 +709,13 @@ Object.extend(String.prototype, (function() { * String#include(substring) -> Boolean * * Checks if the string contains `substring`. + * + * ##### Example + * + * 'Prototype framework'.include('frame'); + * //-> true + * 'Prototype framework'.include('frameset'); + * //-> false **/ function include(pattern) { return this.indexOf(pattern) > -1; @@ -706,6 +725,11 @@ Object.extend(String.prototype, (function() { * String#startsWith(substring) -> Boolean * * Checks if the string starts with `substring`. + * + * ##### Example + * + * 'Prototype JavaScript'.startsWith('Pro'); + * //-> true **/ function startsWith(pattern) { // We use `lastIndexOf` instead of `indexOf` to avoid tying execution @@ -717,6 +741,11 @@ Object.extend(String.prototype, (function() { * String#endsWith(substring) -> Boolean * * Checks if the string ends with `substring`. + * + * ##### Example + * + * 'slaughter'.endsWith('laughter') + * // -> true **/ function endsWith(pattern) { var d = this.length - pattern.length; @@ -729,6 +758,14 @@ Object.extend(String.prototype, (function() { * String#empty() -> Boolean * * Checks if the string is empty. + * + * ##### Example + * + * ''.empty(); + * //-> true + * + * ' '.empty(); + * //-> false **/ function empty() { return this == ''; @@ -739,6 +776,17 @@ Object.extend(String.prototype, (function() { * * Check if the string is "blank" — either empty (length of `0`) or containing * only whitespace. + * + * ##### Example + * + * ''.blank(); + * //-> true + * + * ' '.blank(); + * //-> true + * + * ' a '.blank(); + * //-> false **/ function blank() { return /^\s*$/.test(this); From 288af7cd0c136cc336ef0013d6cb5869bf167e22 Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Wed, 17 Feb 2010 02:41:50 +0100 Subject: [PATCH 157/502] doc: Merge/update old String docs into source [#126 state:fixed_in_branch] --- src/lang/string.js | 47 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/lang/string.js b/src/lang/string.js index cf5bdec0e..67e1e27c5 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -202,6 +202,20 @@ Object.extend(String.prototype, (function() { * * Truncates a string to given `length` and appends `suffix` to it (indicating * that it is only an excerpt). + * + * ##### Examples + * + * 'A random sentence whose length exceeds 30 characters.'.truncate(); + * // -> 'A random sentence whose len...' + * + * 'Some random text'.truncate(); + * // -> 'Some random text.' + * + * 'Some random text'.truncate(10); + * // -> 'Some ra...' + * + * 'Some random text'.truncate(10, ' [...]'); + * // -> 'Some [...]' **/ function truncate(length, truncation) { length = length || 30; @@ -214,6 +228,11 @@ Object.extend(String.prototype, (function() { * String#strip() -> String * * Strips all leading and trailing whitespace from a string. + * + * ##### Example + * + * ' hello world! '.strip(); + * // -> 'hello world!' **/ function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); @@ -366,10 +385,15 @@ Object.extend(String.prototype, (function() { return this.extractScripts().map(function(script) { return eval(script) }); } - /** + /** related to: String#unescapeHTML * String#escapeHTML() -> String * * Converts HTML special characters to their entity equivalents. + * + * ##### Example + * + * '
    This is an article
    '.escapeHTML(); + * // -> "<div class="article">This is an article</div>" **/ function escapeHTML() { return this.replace(/&/g,'&').replace(//g,'>'); @@ -380,6 +404,14 @@ Object.extend(String.prototype, (function() { * * Strips tags and converts the entity forms of special HTML characters * to their normal form. + * + * ##### Examples + * + * 'x > 10'.unescapeHTML() + * // -> 'x > 10' + * + * '

    Pride & Prejudice

    ;'.unescapeHTML() + * // -> '

    Pride & Prejudice

    ' **/ function unescapeHTML() { // Warning: In 1.7 String#unescapeHTML will no longer call String#stripTags. @@ -475,6 +507,14 @@ Object.extend(String.prototype, (function() { * Used internally by ObjectRange. * Converts the last character of the string to the following character in * the Unicode alphabet. + * + * ##### Examples + * + * 'a'.succ(); + * // -> 'b' + * + * 'aaaa'.succ(); + * // -> 'aaab' **/ function succ() { return this.slice(0, this.length - 1) + @@ -485,6 +525,11 @@ Object.extend(String.prototype, (function() { * String#times(count) -> String * * Concatenates the string `count` times. + * + * ##### Example + * + * "echo ".times(3); + * // -> "echo echo echo " **/ function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); From 7a23aee06b606f622808b8344da099a4e5632e4f Mon Sep 17 00:00:00 2001 From: dandean Date: Tue, 16 Feb 2010 22:07:20 -0800 Subject: [PATCH 158/502] doc: Merge/update old Object.isXYZ docs into source [#116 state:fixed_in_branch] --- src/lang/object.js | 93 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 5719e09a5..85c0e6010 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -283,6 +283,20 @@ * - object (Object): The object to test. * * Returns `true` if `object` is a DOM node of type 1; `false` otherwise. + * + * ##### Examples + * + * Object.isElement(new Element('div')); + * //-> true + * + * Object.isElement(document.createElement('div')); + * //-> true + * + * Object.isElement($('id_of_an_exiting_element')); + * //-> true + * + * Object.isElement(document.createTextNode('foo')); + * //-> false **/ function isElement(object) { return !!(object && object.nodeType == 1); @@ -292,7 +306,18 @@ * Object.isArray(object) -> Boolean * - object (Object): The object to test. * - * Returns `true` if `object` is an array; false otherwise. + * Returns `true` if `object` is an [[Array]]; `false` otherwise. + * + * ##### Examples + * + * Object.isArray([]); + * //-> true + * + * Object.isArray($w()); + * //-> true + * + * Object.isArray({ }); + * //-> false **/ function isArray(object) { return _toString.call(object) == "[object Array]"; @@ -311,6 +336,17 @@ * * Returns `true` if `object` is an instance of the [[Hash]] class; `false` * otherwise. + * + * ##### Examples + * + * Object.isHash(new Hash({ })); + * //-> true + * + * Object.isHash($H({ })); + * //-> true + * + * Object.isHash({ }); + * //-> false **/ function isHash(object) { return object instanceof Hash; @@ -320,7 +356,15 @@ * Object.isFunction(object) -> Boolean * - object (Object): The object to test. * - * Returns `true` if `object` is of type `function`; `false` otherwise. + * Returns `true` if `object` is of type [[Function]]; `false` otherwise. + * + * ##### Examples + * + * Object.isFunction($); + * //-> true + * + * Object.isFunction(123); + * //-> false **/ function isFunction(object) { return typeof object === "function"; @@ -330,7 +374,18 @@ * Object.isString(object) -> Boolean * - object (Object): The object to test. * - * Returns `true` if `object` is of type `string`; `false` otherwise. + * Returns `true` if `object` is of type [[String]]; `false` otherwise. + * + * ##### Examples + * + * Object.isString("foo"); + * //-> true + * + * Object.isString(""); + * //-> true + * + * Object.isString(123); + * //-> false **/ function isString(object) { return _toString.call(object) == "[object String]"; @@ -340,7 +395,18 @@ * Object.isNumber(object) -> Boolean * - object (Object): The object to test. * - * Returns `true` if `object` is of type `number`; `false` otherwise. + * Returns `true` if `object` is of type [[Number]]; `false` otherwise. + * + * ##### Examples + * + * Object.isNumber(0); + * //-> true + * + * Object.isNumber(1.2); + * //-> true + * + * Object.isNumber("foo"); + * //-> false **/ function isNumber(object) { return _toString.call(object) == "[object Number]"; @@ -350,7 +416,24 @@ * Object.isUndefined(object) -> Boolean * - object (Object): The object to test. * - * Returns `true` if `object` is of type `string`; `false` otherwise. + * Returns `true` if `object` is of type `undefined`; `false` otherwise. + * + * ##### Examples + * + * Object.isUndefined(); + * //-> true + * + * Object.isUndefined(undefined); + * //-> true + * + * Object.isUndefined(null); + * //-> false + * + * Object.isUndefined(0); + * //-> false + * + * Object.isUndefined(""); + * //-> false **/ function isUndefined(object) { return typeof object === "undefined"; From 4f447955a4d786498b3c9c51e81a4510b30afb4e Mon Sep 17 00:00:00 2001 From: Bertrand Chardon Date: Sat, 21 Nov 2009 20:34:19 +0100 Subject: [PATCH 159/502] doc: Merge/update old doc for Object.clone and Object.extend into source [#117 state:fixed_in_branch] --- src/lang/object.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 85c0e6010..74080ab1b 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -23,14 +23,22 @@ var _toString = Object.prototype.toString; - /** - * Object.extend(destination, source) -> Object - * - destination (Object): The object to receive the new properties. - * - source (Object): The object whose properties will be duplicated. - * - * Copies all properties from the source to the destination object. Returns - * the destination object. - **/ +/** + * Object.extend(destination, source) -> Object + * - destination (Object): The object to receive the new properties. + * - source (Object): The object whose properties will be duplicated. + * + * Copies all properties from the source to the destination object. Used by Prototype + * to simulate inheritance (rather statically) by copying to prototypes. + * + * Documentation should soon become available that describes how Prototype implements + * OOP, where you will find further details on how Prototype uses [[Object.extend]] and + * [[Class.create]] (something that may well change in version 2.0). It will be linked + * from here. + * + * Do not mistake this method with its quasi-namesake [[Element.extend]], + * which implements Prototype's (much more complex) DOM extension mechanism. +**/ function extend(destination, source) { for (var property in source) destination[property] = source[property]; @@ -253,26 +261,29 @@ * Do note that this is a _shallow_ copy, not a _deep_ copy. Nested objects * will retain their references. * - *
    Examples
    + * ##### Examples * * var original = {name: 'primaryColors', values: ['red', 'green', 'blue']}; * var copy = Object.clone(original); + * * original.name; * // -> "primaryColors" * original.values[0]; * // -> "red" * copy.name; * // -> "primaryColors" + * * copy.name = "secondaryColors"; * original.name; * // -> "primaryColors" * copy.name; * // -> "secondaryColors" + * * copy.values[0] = 'magenta'; * copy.values[1] = 'cyan'; * copy.values[2] = 'yellow'; * original.values[0]; - * // -> "magenta" (it was a shallow copy, so they shared the array) + * // -> "magenta" (it's a shallow copy, so they share the array) **/ function clone(object) { return extend({ }, object); From d5fe7d58cf2a3a86d2034f3f915ea4ed62229e9f Mon Sep 17 00:00:00 2001 From: Bertrand Chardon Date: Fri, 20 Nov 2009 22:45:17 +0100 Subject: [PATCH 160/502] doc: Merge/update old doc for Element.setStyle, Element.getStyle and Element.setOpacity [#79 state:fixed_in_branch] --- src/dom/dom.js | 109 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 12 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 339855582..fea77845d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1858,7 +1858,46 @@ Element.Methods = { * Returns the given CSS property value of `element`. The property can be * specified in either its CSS form (`font-size`) or its camelized form * (`fontSize`). - **/ + * This method looks up the CSS property of an element whether it was applied inline + * or in a stylesheet. It works around browser inconsistencies regarding `float`, `opacity`, + * which returns a value between `0` (fully transparent) and `1` (fully opaque), position + * properties (`left`, `top`, `right` and `bottom`) and when getting the dimensions (`width` + * or `height`) of hidden elements. + * + * ##### Examples + * + * $(element).getStyle('font-size'); + * // equivalent: + * + * $(element).getStyle('fontSize'); + * // -> '12px' + * + * ##### Notes + * + * Internet Explorer returns literal values while other browsers return computed values. + * Consider the following HTML snippet: + * + * language: html + * + *
    + * + * + * $('test').getStyle('margin-left'); + * // -> '1em' in Internet Explorer, + * // -> '12px' elsewhere. + * + * Safari returns `null` for *any* non-inline property if the element is hidden (has + * `display` set to `'none'`). + * + * Not all CSS shorthand properties are supported. You may only use the CSS properties + * described in the [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). + **/ + getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); @@ -1880,14 +1919,44 @@ Element.Methods = { return $(element).getStyle('opacity'); }, - /** + /** * Element.setStyle(@element, styles) -> Element - * - * Modifies `element`'s CSS style properties. - * - * Styles are passed as an object of property-value pairs in which the - * properties are specified in their camelized form (e.g., `fontSize`). + * + * Modifies `element`'s CSS style properties. Styles are passed as a hash of property-value + * pairs in which the properties are specified in their camelized form. + * + * ##### Examples + * + * $(element).setStyle({ + * backgroundColor: '#900', + * fontSize: '12px' + * }); + * // -> Element + * + * ##### Notes + * + * The method transparently deals with browser inconsistencies for `float` (however, + * as `float` is a reserved keyword, you must either escape it or use `cssFloat` instead) and + * `opacity` (which accepts values between `0` -fully transparent- and `1` -fully opaque-). + * You can safely use either of the following across all browsers: + * + * $(element).setStyle({ + * cssFloat: 'left', + * opacity: 0.5 + * }); + * // -> Element + * + * $(element).setStyle({ + * 'float': 'left', // notice how float is surrounded by single quotes + * opacity: 0.5 + * }); + * // -> Element + * + * Not all CSS shorthand properties are supported. You may only use the CSS properties + * described in the [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). + * **/ + setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; @@ -1906,11 +1975,27 @@ Element.Methods = { return element; }, - /** - * Element.setOpacity(@element, value) -> Element - * - * Sets the opacity of `element`. - **/ + /** + * Element.setOpacity(@element, opacity) -> [Element...] + * + * Sets the visual opacity of an element while working around inconsistencies in various + * browsers. The `opacity` argument should be a floating point number, where the value + * of `0` is fully transparent and `1` is fully opaque. + * + * [[Element.setStyle]] method uses [[Element.setOpacity]] internally when needed. + * + * ##### Examples + * + * var element = $('myelement'); + * // set to 50% transparency + * element.setOpacity(0.5); + * + * // these are equivalent, but allow for setting more than + * // one CSS property at once: + * element.setStyle({ opacity: 0.5 }); + * element.setStyle("opacity: 0.5"); + **/ + setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : From 89cad667dc87a59fb3b347f7168b68844e3adf64 Mon Sep 17 00:00:00 2001 From: Gianni Chiappetta Date: Tue, 16 Feb 2010 01:18:45 -0500 Subject: [PATCH 161/502] doc: corrected Ajax callback argument [#143 state:fixed_in_branch] --- src/ajax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ajax.js b/src/ajax.js index 88a1f5295..f828e9943 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -67,7 +67,7 @@ *
    Common callbacks
    * * When used on individual instances, all callbacks (except `onException`) are - * invoked with two parameters: the `XMLHttpRequest` object and the result of + * invoked with two parameters: the [[Ajax.Response]] object and the result of * evaluating the `X-JSON` response header, if any (can be `null`). * * For another way of describing their chronological order and which callbacks From acf9eea3d4c0c7d4e2d1609e4e500b34e947d021 Mon Sep 17 00:00:00 2001 From: Rob Sterner Date: Sun, 27 Dec 2009 15:55:34 -0500 Subject: [PATCH 162/502] doc: Merge/update old Object.inspect doc into source [#118 state:fixed_in_branch] --- src/lang/object.js | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index 74080ab1b..b3645f746 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -46,20 +46,36 @@ } /** - * Object.inspect(object) -> String + * Object.inspect(obj) -> String * - object (Object): The item to be inspected. - * + * * Returns the debug-oriented string representation of the object. - * - * `undefined` and `null` are represented as such. - * - * Other types are checked for a `inspect` method. If there is one, it is - * used; otherwise, it reverts to the `toString` method. - * - * Prototype provides `inspect` methods for many types, both built-in and - * library-defined — among them `String`, `Array`, `Enumerable` and `Hash`. - * These attempt to provide useful string representations (from a - * developer's standpoint) for their respective types. + * + * * `undefined` and `null` are represented as such. + * * Other types are looked up for a `inspect` method: if there is one, it is used, otherwise, + * it reverts to the `toString` method. + * + * Prototype provides `inspect` methods for many types, both built-in and library-defined, + * such as in [[String#inspect]], [[Array#inspect]], [[Enumerable#inspect]] and [[Hash#inspect]], + * which attempt to provide most-useful string representations (from a developer's standpoint) + * for their respective types. + * + * ##### Examples + * + * Object.inspect(); + * // -> 'undefined' + * + * Object.inspect(null); + * // -> 'null' + * + * Object.inspect(false); + * // -> 'false' + * + * Object.inspect([1, 2, 3]); + * // -> '[1, 2, 3]' + * + * Object.inspect('hello'); + * // -> "'hello'" **/ function inspect(object) { try { From e7b9b8f64b086e8fce140779c06f39df0881ed9b Mon Sep 17 00:00:00 2001 From: RStankov Date: Wed, 17 Feb 2010 18:27:20 +0200 Subject: [PATCH 163/502] Hash.set returns the setted value --- src/dom/layout.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 00b3d3f3c..5726db8ad 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -340,10 +340,7 @@ if (!(property in COMPUTATIONS)) { throw "Property not found."; } - - var value = COMPUTATIONS[property].call(this, this.element); - this._set(property, value); - return value; + return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, /** From 4a487cc2d3c69dafe844a7e0455416af1fdd2028 Mon Sep 17 00:00:00 2001 From: RStankov Date: Wed, 17 Feb 2010 18:36:41 +0200 Subject: [PATCH 164/502] add preCompute argument to Element.getLayout --- src/dom/layout.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 5726db8ad..a0cc90aef 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -651,8 +651,8 @@ }); /** - * Element.getLayout(@element) -> Element.Layout - * + * Element.getLayout(@element, preCompute) -> Element.Layout + * * Returns an instance of [[Element.Layout]] for measuring an element's * dimensions. * @@ -663,8 +663,8 @@ * `Element.getLayout` again only when the values in an existing * `Element.Layout` object have become outdated. **/ - function getLayout(element) { - return new Element.Layout(element); + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); } /** From 8ad626e3991434dd2cefc50688e1ec805f467df3 Mon Sep 17 00:00:00 2001 From: RStankov Date: Wed, 17 Feb 2010 18:54:52 +0200 Subject: [PATCH 165/502] initial test for preComputing --- test/unit/layout_test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 46180c580..bd1592131 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -14,7 +14,17 @@ function isDisplayed(element) { new Test.Unit.Runner({ setup: function() { }, - + 'test preCompute argument of layout': function() { + var preComputedLayout = $('box1').getLayout(true), + normalLayout = $('box1').getLayout(); + + // restore normal get method from Hash object + preComputedLayout.get = Hash.prototype.get; + + Element.Layout.PROPERTIES.each(function(key) { + this.assertEqual(normalLayout.get(key), preComputedLayout.get(key), key); + }, this); + }, 'test layout on absolutely-positioned elements': function() { var layout = $('box1').getLayout(); From b8e087058d845b343e344900ddfd58365371aa48 Mon Sep 17 00:00:00 2001 From: RStankov Date: Wed, 17 Feb 2010 18:57:57 +0200 Subject: [PATCH 166/502] optimize preComputing, by removing 2+n checks and one anonymous function --- src/dom/layout.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index a0cc90aef..8bdaaf41c 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -219,17 +219,13 @@ if (preCompute) { this._preComputing = true; this._begin(); - } - Element.Layout.PROPERTIES.each( function(property) { - if (preCompute) { - this._compute(property); - } else { - this._set(property, null); - } - }, this); - if (preCompute) { + Element.Layout.PROPERTIES.each( this._compute, this ); this._end(); this._preComputing = false; + } else { + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); } }, From 18bf4c8fef1cfe1d475068e610e8785ed6785f49 Mon Sep 17 00:00:00 2001 From: RStankov Date: Wed, 17 Feb 2010 21:33:14 +0200 Subject: [PATCH 167/502] always nullify all properties keys (before preCoputing, since in get we check for null, an not undefined) --- src/dom/layout.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 8bdaaf41c..d92520ed7 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -213,6 +213,12 @@ initialize: function($super, element, preCompute) { $super(); this.element = $(element); + + // nullify all properties keys + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + // The 'preCompute' boolean tells us whether we should fetch all values // at once. If so, we should do setup/teardown only once. We set a flag // so that we can ignore calls to `_begin` and `_end` elsewhere. @@ -222,10 +228,6 @@ Element.Layout.PROPERTIES.each( this._compute, this ); this._end(); this._preComputing = false; - } else { - Element.Layout.PROPERTIES.each( function(property) { - this._set(property, null); - }, this); } }, From 90aa7e26126186f1b8117a91c0cdd7358cd9e05c Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Wed, 17 Feb 2010 22:04:27 -0800 Subject: [PATCH 168/502] doc: Merge/update old Ajax.Responders docs into source [#69 state:fixed_in_branch] --- src/ajax/responders.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ajax/responders.js b/src/ajax/responders.js index c9ed24485..daa93263a 100644 --- a/src/ajax/responders.js +++ b/src/ajax/responders.js @@ -34,7 +34,7 @@ * } * }); * - *
    Responder callbacks
    + * ##### Responder callbacks * * The callbacks for responders are similar to the callbacks described in * the [[Ajax section]], but take a different signature. They're invoked with @@ -64,7 +64,6 @@ * the request completes, status-specific callbacks are called, and possible * automatic behaviors are processed. Guaranteed to run regardless of what * happened during the request. - * **/ Ajax.Responders = { From 60ed1912a7565baa69ba61134b108713bd414a38 Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 18 Feb 2010 10:27:03 -0800 Subject: [PATCH 169/502] doc: Merge old Element.Methods.nextSiblings/previousSiblings docs into source. Update Element.siblings to match. [#72 state:fixed_in_branch] --- src/dom/dom.js | 65 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index fea77845d..5a90df12b 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -915,7 +915,34 @@ Element.Methods = { * Element.previousSiblings(@element) -> [Element...] * * Collects all of `element`'s previous siblings and returns them as an - * array of elements. + * [[Array]] of elements. + * + * Two elements are siblings if they have the same parent. So for example, + * the `head` and `body` elements are siblings (their parent is the `html` + * element). Previous-siblings are simply the ones which precede `element` in + * the document. + * + * The returned [[Array]] reflects the siblings _inversed_ order in the document + * (e.g. an index of 0 refers to the lowest sibling i.e., the one closest to + * `element`). + * + * Note that all of Prototype's DOM traversal methods ignore text nodes and + * return element nodes only. + * + * ##### Examples + * + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    + * + * $('mcintosh').previousSiblings(); + * // -> [li#mutsu, li#golden-delicious] + * + * $('golden-delicious').previousSiblings(); + * // -> [] **/ previousSiblings: function(element, maximumLength) { return Element.recursivelyCollect(element, 'previousSibling'); @@ -924,8 +951,34 @@ Element.Methods = { /** * Element.nextSiblings(@element) -> [Element...] * - * Collects all of `element`'s next siblings and returns them as an array + * Collects all of `element`'s next siblings and returns them as an [[Array]] * of elements. + * + * Two elements are siblings if they have the same parent. So for example, + * the `head` and `body` elements are siblings (their parent is the `html` + * element). Next-siblings are simply the ones which follow `element` in the + * document. + * + * The returned [[Array]] reflects the siblings order in the document + * (e.g. an index of 0 refers to the sibling right below `element`). + * + * Note that all of Prototype's DOM traversal methods ignore text nodes and + * return element nodes only. + * + * ##### Examples + * + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • Ida Red
    • + *
    + * + * $('mutsu').nextSiblings(); + * // -> [li#mcintosh, li#ida-red] + * + * $('ida-red').nextSiblings(); + * // -> [] **/ nextSiblings: function(element) { return Element.recursivelyCollect(element, 'nextSibling'); @@ -933,17 +986,15 @@ Element.Methods = { /** * Element.siblings(@element) -> [Element...] - * Collects all of element's siblings and returns them as an array of + * Collects all of element's siblings and returns them as an [[Array]] of * elements. * - * ##### More information - * * Two elements are siblings if they have the same parent. So for example, * the `head` and `body` elements are siblings (their parent is the `html` * element). * - * The returned array reflects the siblings' order in the document (e.g. an - * index of 0 refers to `element`'s topmost sibling). + * The returned [[Array]] reflects the siblings' order in the document (e.g. + * an index of 0 refers to `element`'s topmost sibling). * * Note that all of Prototype's DOM traversal methods ignore text nodes and * return element nodes only. From 611b31dbd2da7f3a7689230a6c2ae12b35821c6f Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 18 Feb 2010 18:05:03 -0800 Subject: [PATCH 170/502] doc: Merge/udpate old Element.Methods.getDimensions (et al) docs into source [#77 state:fixed_in_branch] --- src/dom/dom.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 5a90df12b..cd62b0af9 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1663,6 +1663,22 @@ Element.Methods = { * Element.getHeight(@element) -> Number * * Returns the height of `element`. + * + * This method returns correct values on elements whose display is set to + * `none` either in an inline style rule or in an CSS stylesheet. + * + * For performance reasons, if you need to query both width _and_ height of + * `element`, you should consider using [[Element.getDimensions]] instead. + * + * Note that the value returned is a _number only_ although it is + * _expressed in pixels_. + * + * ##### Examples + * + *
    + * + * $('rectangle').getHeight(); + * // -> 100 **/ getHeight: function(element) { return Element.getDimensions(element).height; @@ -1672,6 +1688,22 @@ Element.Methods = { * Element.getWidth(@element) -> Number * * Returns the width of `element`. + * + * This method returns correct values on elements whose display is set to + * `none` either in an inline style rule or in an CSS stylesheet. + * + * For performance reasons, if you need to query both width _and_ height of + * `element`, you should consider using [[Element.getDimensions]] instead. + * + * Note that the value returned is a _number only_ although it is + * _expressed in pixels_. + * + * ##### Examples + * + *
    + * + * $('rectangle').getWidth(); + * // -> 200 **/ getWidth: function(element) { return Element.getDimensions(element).width; @@ -2059,6 +2091,30 @@ Element.Methods = { * * Finds the computed width and height of `element` and returns them as * key/value pairs of an object. + * + * This method returns correct values on elements whose display is set to + * `none` either in an inline style rule or in an CSS stylesheet. + * + * In order to avoid calling the method twice, you should consider caching + * the values returned in a variable as shown below. If you only need + * `element`'s width or height, consider using [[Element.getWidth]] or + * [[Element.getHeight]] instead. + * + * Note that all values are returned as _numbers only_ although they are + * _expressed in pixels_. + * + * ##### Examples + * + *
    + * + * var dimensions = $('rectangle').getDimensions(); + * // -> {width: 200, height: 100} + * + * dimensions.width; + * // -> 200 + * + * dimensions.height; + * // -> 100 **/ getDimensions: function(element) { element = $(element); From df44989261a07714e92ff0da378064b6c567f3d4 Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Thu, 18 Feb 2010 20:25:53 -0800 Subject: [PATCH 171/502] doc: Merge/update old Form.Element overview docs into source [#106 state:fixed_in_branch] --- src/dom/form.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index b22548006..d739817da 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -377,8 +377,9 @@ Form.Methods = { * Utilities for dealing with form controls in the DOM. * * This is a collection of methods that assist in dealing with form controls. - * They provide ways to focus, serialize, disable/enable or extract current - * value from a specific control. + * They provide ways to [[Form.Element.focus focus]], [[Form.Element.serialize + * serialize]], [[Form.Element.disable disable]]/[[Form.Element.enable enable]] + * or extract current value from a specific control. * * Note that nearly all these methods are available directly on `input`, * `select`, and `textarea` elements. Therefore, these are equivalent: @@ -388,7 +389,7 @@ Form.Methods = { * * Naturally, you should always prefer the shortest form suitable in a * situation. Most of these methods also return the element itself (as - * indicated by the return type) for chainability. + * indicated by the return type) for chainability. **/ Form.Element = { From 6de18c4f2fd414482db9b97197cda819aeec9acd Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 20 Feb 2010 22:32:57 -0600 Subject: [PATCH 172/502] PDoc fix. --- src/dom/layout.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index d92520ed7..dde85d69a 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -351,9 +351,9 @@ * Keys can be passed into this method as individual arguments _or_ * separated by spaces within a string. * - * // Equivalent statements: - * someLayout.toCSS('top', 'bottom', 'left', 'right'); - * someLayout.toCSS('top bottom left right'); + * // Equivalent statements: + * someLayout.toCSS('top', 'bottom', 'left', 'right'); + * someLayout.toCSS('top bottom left right'); * * Useful for passing layout properties to [[Element.setStyle]]. **/ From 732eb3d4b29309e62b11fb6ff06211662c78af1b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 20 Feb 2010 22:35:37 -0600 Subject: [PATCH 173/502] Change how PDoc is told which syntax highlighting engine to use. SYNTAX_HIGHLIGHTER environment variable will instruct PDoc to use a specific engine. If omitted, will try pygments, coderay, and none, in order of preference. --- Rakefile | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 2b4e2ebaf..a61f3fd90 100755 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,11 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] + DEFAULT_SELECTOR_ENGINE = 'sizzle' + + # Possible options for PDoc syntax highlighting, in order of preference. + SYNTAX_HIGHLIGHTERS = [:pygments, :coderay, :none] %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') @@ -36,7 +40,7 @@ module PrototypeHelper puts " http://book.git-scm.com/2_installing_git.html" exit end - + def self.sprocketize(options = {}) options = { :destination => File.join(DIST_DIR, options[:source]), @@ -72,17 +76,63 @@ module PrototypeHelper ) rm_rf DOC_DIR + highlighter = syntax_highlighter + puts "Using syntax highlighter: #{highlighter}\n" + PDoc.run({ :source_files => [temp_path], :destination => DOC_DIR, :index_page => 'README.markdown', - :syntax_highlighter => :pygments, + :syntax_highlighter => highlighter, :markdown_parser => :bluecloth }) rm_rf temp_path end + def self.syntax_highlighter + if ENV['SYNTAX_HIGHLIGHTER'] + highlighter = ENV['SYNTAX_HIGHLIGHTER'].to_sym + require_highlighter(highlighter, true) + return highlighter + end + + SYNTAX_HIGHLIGHTERS.detect { |n| require_highlighter(n) } + end + + def self.require_highlighter(name, verbose=false) + case name + when :pygments + success = system("pygmentize -V") + if !success && verbose + puts "\nYou asked to use Pygments, but I can't find the 'pygmentize' binary." + puts "To install, visit:\n" + puts " http://pygments.org/docs/installation/\n\n" + exit + end + return success # (we have pygments) + when :coderay + begin + require 'coderay' + rescue LoadError => e + if verbose + puts "\nYou asked to use CodeRay, but I can't find the 'coderay' gem. Just run:\n\n" + puts " $ gem install coderay" + puts "\nand you should be all set.\n\n" + exit + end + return false + end + return true # (we have CodeRay) + when :none + return true + else + puts "\nYou asked to use a syntax highlighter I don't recognize." + puts "Valid options: #{SYNTAX_HIGHLIGHTERS.join(', ')}\n\n" + exit + end + end + def self.require_sprockets require_submodule('Sprockets', 'sprockets') end @@ -161,7 +211,7 @@ namespace :doc do desc "Builds the documentation." task :build => [:require] do PrototypeHelper.build_doc_for(ENV['SECTION'] ? "#{ENV['SECTION']}.js" : 'prototype.js') - end + end task :require do PrototypeHelper.require_pdoc From 038a2985a70593c1a86c230fadbdfe2e4898a48c Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 22 Feb 2010 00:46:29 +0100 Subject: [PATCH 174/502] Make Prototype's JSON implementation EcmaScript 5 compliant. --- CHANGELOG | 2 + src/lang/array.js | 22 +---------- src/lang/date.js | 71 +++++++++++++++++++++++++----------- src/lang/hash.js | 27 +++++--------- src/lang/number.js | 10 ----- src/lang/object.js | 49 +++++++++++++++++++++---- src/lang/string.js | 34 ++++++++++------- test/unit/ajax_test.js | 9 ++--- test/unit/array_test.js | 7 ---- test/unit/date_test.js | 6 ++- test/unit/fixtures/data.json | 2 +- test/unit/hash_test.js | 4 +- test/unit/number_test.js | 6 --- test/unit/object_test.js | 20 +++++----- test/unit/string_test.js | 9 ----- 15 files changed, 146 insertions(+), 132 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 91c7eda74..49e4b8302 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Make Prototype's JSON implementation EcmaScript 5 compliant. [#453 state:resolved] (Tobie Langel) + * Also detect embedded (UIWebView) mobile Safari. (Thomas Fuchs) * Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. Replace with feature test and a simple boolean check at runtime. (kangax) diff --git a/src/lang/array.js b/src/lang/array.js index 389a1228d..8d206791b 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -334,25 +334,6 @@ Array.from = $A; return '[' + this.map(Object.inspect).join(', ') + ']'; } - /** related to: Object.toJSON - * Array#toJSON() -> String - * - * Returns a JSON string representation of the array. - * - *
    Example
    - * - * ['a', {b: null}].toJSON(); - * //-> '["a", {"b": null}]' - **/ - function toJSON() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; - } - /** * Array#indexOf(item[, offset = 0]) -> Number * - item (?): A value that may or may not be in the array. @@ -432,8 +413,7 @@ Array.from = $A; clone: clone, toArray: clone, size: size, - inspect: inspect, - toJSON: toJSON + inspect: inspect }); // fix for opera diff --git a/src/lang/date.js b/src/lang/date.js index 1e2494a39..2739b8c6b 100644 --- a/src/lang/date.js +++ b/src/lang/date.js @@ -4,26 +4,53 @@ * Extensions to the built-in `Date` object. **/ -/** - * Date#toJSON() -> String - * - * Produces a string representation of the date in ISO 8601 format. - * The time zone is always UTC, as denoted by the suffix "Z". - * - *
    Example
    - * - * var d = new Date(1969, 11, 31, 19); - * d.getTimezoneOffset(); - * //-> -180 (time offest is given in minutes.) - * d.toJSON(); - * //-> '"1969-12-31T16:00:00Z"' -**/ -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; + +(function(proto) { + + /** + * Date#toISOString() -> String + * + * Produces a string representation of the date in ISO 8601 format. + * The time zone is always UTC, as denoted by the suffix "Z". + * + *
    Example
    + * + * var d = new Date(1969, 11, 31, 19); + * d.getTimezoneOffset(); + * //-> -180 (time offest is given in minutes.) + * d.toISOString(); + * //-> '1969-12-31T16:00:00Z' + **/ + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; + } + + /** + * Date#toJSON() -> String + * + * Internally calls [[Date#toISOString]]. + * + *
    Example
    + * + * var d = new Date(1969, 11, 31, 19); + * d.getTimezoneOffset(); + * //-> -180 (time offest is given in minutes.) + * d.toJSON(); + * //-> '1969-12-31T16:00:00Z' + **/ + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); diff --git a/src/lang/hash.js b/src/lang/hash.js index 3ff86c04c..99189dc8c 100644 --- a/src/lang/hash.js +++ b/src/lang/hash.js @@ -168,6 +168,14 @@ var Hash = Class.create(Enumerable, (function() { return Object.clone(this._object); } + /** related to: Object.toJSON, alias of: Hash#toObject + * Hash#toJSON() -> Object + **/ + + /** alias of: Hash#toObject + * Hash#toTemplateReplacements() -> Object + **/ + /** * Hash#keys() -> [String...] * @@ -331,22 +339,7 @@ var Hash = Class.create(Enumerable, (function() { return pair.map(Object.inspect).join(': '); }).join(', ') + '}>'; } - - /** related to: Object.toJSON - * Hash#toJSON() -> String - * - * Returns a JSON string containing the keys and values in this hash. - * - *
    Example
    - * - * var h = $H({'a': 'apple', 'b': 23, 'c': false}); - * h.toJSON(); - * // -> {"a": "apple", "b": 23, "c": false} - **/ - function toJSON() { - return Object.toJSON(this.toObject()); - } - + /** * Hash#clone() -> Hash * @@ -371,7 +364,7 @@ var Hash = Class.create(Enumerable, (function() { update: update, toQueryString: toQueryString, inspect: inspect, - toJSON: toJSON, + toJSON: toObject, clone: clone }; })()); diff --git a/src/lang/number.js b/src/lang/number.js index 459d9dca6..63e7dd272 100644 --- a/src/lang/number.js +++ b/src/lang/number.js @@ -103,15 +103,6 @@ Object.extend(Number.prototype, (function() { return '0'.times(length - string.length) + string; } - /** related to: Object.toJSON - * Number#toJSON() -> String - * - * Returns a JSON string representation of the number. - **/ - function toJSON() { - return isFinite(this) ? this.toString() : 'null'; - } - /** * Number#abs() -> Number * @@ -159,7 +150,6 @@ Object.extend(Number.prototype, (function() { succ: succ, times: times, toPaddedString: toPaddedString, - toJSON: toJSON, abs: abs, round: round, ceil: ceil, diff --git a/src/lang/object.js b/src/lang/object.js index e791d603a..64da68993 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -10,8 +10,12 @@ **/ (function() { - var _toString = Object.prototype.toString; - + var _toString = Object.prototype.toString, + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + /** * Object.extend(destination, source) -> Object * - destination (Object): The object to receive the new properties. @@ -67,26 +71,55 @@ * generic `Object`. **/ function toJSON(object) { - var type = typeof object; + var results, type = typeof object; + switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); + case 'string': return object.inspect(true); + case 'number': return _numberToJSON(object); } if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); + + type = _toString.call(object); + + switch (type) { + case '[object Boolean]': return object.toString(); + case '[object String]': return object.inspect(true); + case '[object Number]': return _numberToJSON(object); + case '[object Array]': + results = []; + object.each(function(item) { + item = toJSON(item); + if (isUndefined(item)) item = 'null'; + results.push(item); + }); + return '[' + results.join(',') + ']'; + } + + if (typeof object.toJSON === 'function') + return toJSON(object.toJSON()); if (isElement(object)) return; - var results = []; + results = []; for (var property in object) { var value = toJSON(object[property]); if (!isUndefined(value)) - results.push(property.toJSON() + ': ' + value); + results.push(property.inspect(true) + ':' + value); } - return '{' + results.join(', ') + '}'; + return '{' + results.join(',') + '}'; + } + + function _numberToJSON(number) { + return isFinite(number) ? String(number) : 'null'; + } + + function stringify(object) { + return JSON.stringify(object); } /** @@ -279,7 +312,7 @@ extend(Object, { extend: extend, inspect: inspect, - toJSON: toJSON, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, toQueryString: toQueryString, toHTML: toHTML, keys: keys, diff --git a/src/lang/string.js b/src/lang/string.js index e581f6322..29a9c9e4b 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -29,6 +29,9 @@ Object.extend(String, { }); Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; @@ -376,15 +379,6 @@ Object.extend(String.prototype, (function() { return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } - /** related to: Object.toJSON - * String#toJSON() -> String - * - * Returns a JSON string. - **/ - function toJSON() { - return this.inspect(true); - } - /** * String#unfilterJSON([filter = Prototype.JSONFilter]) -> String * @@ -404,8 +398,10 @@ Object.extend(String.prototype, (function() { function isJSON() { var str = this; if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); } /** @@ -418,12 +414,23 @@ Object.extend(String.prototype, (function() { * is _not called_. **/ function evalJSON(sanitize) { - var json = this.unfilterJSON(); + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } /** * String#include(substring) -> Boolean @@ -510,10 +517,9 @@ Object.extend(String.prototype, (function() { underscore: underscore, dasherize: dasherize, inspect: inspect, - toJSON: toJSON, unfilterJSON: unfilterJSON, isJSON: isJSON, - evalJSON: evalJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, include: include, startsWith: startsWith, endsWith: endsWith, diff --git a/test/unit/ajax_test.js b/test/unit/ajax_test.js index 7848a83be..f1f530e24 100644 --- a/test/unit/ajax_test.js +++ b/test/unit/ajax_test.js @@ -2,7 +2,7 @@ var extendDefault = function(options) { return Object.extend({ asynchronous: false, method: 'get', - onException: function(e) { throw e } + onException: function(r, e) { throw e; } }, options); }; @@ -274,8 +274,7 @@ new Test.Unit.Runner({ sanitizeJSON: true, parameters: Fixtures.invalidJson, onException: function(request, error) { - this.assert(error.message.include('Badly formed JSON string')); - this.assertInstanceOf(Ajax.Request, request); + this.assertEqual('SyntaxError', error.name); }.bind(this) })); } else { @@ -360,14 +359,14 @@ new Test.Unit.Runner({ new Ajax.Request("/response", extendDefault({ parameters: Fixtures.invalidJson, onException: function(request, error) { - this.assert(error.message.include('Badly formed JSON string')); + this.assertEqual('SyntaxError', error.name); }.bind(this) })); new Ajax.Request("/response", extendDefault({ parameters: { 'X-JSON': '{});window.attacked = true;({}' }, onException: function(request, error) { - this.assert(error.message.include('Badly formed JSON string')); + this.assertEqual('SyntaxError', error.name); }.bind(this) })); diff --git a/test/unit/array_test.js b/test/unit/array_test.js index 7fa07b09c..516d52a0a 100644 --- a/test/unit/array_test.js +++ b/test/unit/array_test.js @@ -141,13 +141,6 @@ new Test.Unit.Runner({ ); }, - testToJSON: function(){ - this.assertEqual('[]', [].toJSON()); - this.assertEqual('[\"a\"]', ['a'].toJSON()); - this.assertEqual('[\"a\", 1]', ['a', 1].toJSON()); - this.assertEqual('[\"a\", {\"b\": null}]', ['a', {'b': null}].toJSON()); - }, - testReverse: function(){ this.assertEnumEqual([], [].reverse()); this.assertEnumEqual([1], [1].reverse()); diff --git a/test/unit/date_test.js b/test/unit/date_test.js index 6cd68c32b..838a86096 100644 --- a/test/unit/date_test.js +++ b/test/unit/date_test.js @@ -1,5 +1,9 @@ new Test.Unit.Runner({ testDateToJSON: function() { - this.assertEqual('\"1970-01-01T00:00:00Z\"', new Date(Date.UTC(1970, 0, 1)).toJSON()); + this.assertMatch(/^1970-01-01T00:00:00(\.000)?Z$/, new Date(Date.UTC(1970, 0, 1)).toJSON()); + }, + + testDateToISOString: function() { + this.assertMatch(/^1970-01-01T00:00:00(\.000)?Z$/, new Date(Date.UTC(1970, 0, 1)).toISOString()); } }); \ No newline at end of file diff --git a/test/unit/fixtures/data.json b/test/unit/fixtures/data.json index 85391eb93..d1d0f5cbe 100644 --- a/test/unit/fixtures/data.json +++ b/test/unit/fixtures/data.json @@ -1 +1 @@ -{test: 123} \ No newline at end of file +{"test": 123} \ No newline at end of file diff --git a/test/unit/hash_test.js b/test/unit/hash_test.js index cc3d08313..cf3bcbde1 100644 --- a/test/unit/hash_test.js +++ b/test/unit/hash_test.js @@ -146,8 +146,8 @@ new Test.Unit.Runner({ }, testToJSON: function() { - this.assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}', - $H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}).toJSON()); + this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', + Object.toJSON({b: [undefined, false, true, undefined], c: {a: 'hello!'}})); }, testAbilityToContainAnyKey: function() { diff --git a/test/unit/number_test.js b/test/unit/number_test.js index 4e09529b2..2eb43a575 100644 --- a/test/unit/number_test.js +++ b/test/unit/number_test.js @@ -25,12 +25,6 @@ new Test.Unit.Runner({ this.assertEqual('100', (100).toPaddedString(3)); this.assertEqual('1000', (1000).toPaddedString(3)); }, - - testNumberToJSON: function() { - this.assertEqual('null', Number.NaN.toJSON()); - this.assertEqual('0', (0).toJSON()); - this.assertEqual('-293', (-293).toJSON()); - }, testNumberTimes: function() { var results = []; diff --git a/test/unit/object_test.js b/test/unit/object_test.js index 37d62abfa..e24080549 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -37,27 +37,29 @@ new Test.Unit.Runner({ this.assertUndefined(Object.toJSON(undefined)); this.assertUndefined(Object.toJSON(Prototype.K)); this.assertEqual('\"\"', Object.toJSON('')); + this.assertEqual('\"test\"', Object.toJSON('test')); + this.assertEqual('null', Object.toJSON(Number.NaN)); + this.assertEqual('0', Object.toJSON(0)); + this.assertEqual('-293', Object.toJSON(-293)); this.assertEqual('[]', Object.toJSON([])); this.assertEqual('[\"a\"]', Object.toJSON(['a'])); - this.assertEqual('[\"a\", 1]', Object.toJSON(['a', 1])); - this.assertEqual('[\"a\", {\"b\": null}]', Object.toJSON(['a', {'b': null}])); - this.assertEqual('{\"a\": \"hello!\"}', Object.toJSON({a: 'hello!'})); + this.assertEqual('[\"a\",1]', Object.toJSON(['a', 1])); + this.assertEqual('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}])); + this.assertEqual('{\"a\":\"hello!\"}', Object.toJSON({a: 'hello!'})); this.assertEqual('{}', Object.toJSON({})); this.assertEqual('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K})); - this.assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}', + this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})); - this.assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}', + this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}))); this.assertEqual('true', Object.toJSON(true)); this.assertEqual('false', Object.toJSON(false)); this.assertEqual('null', Object.toJSON(null)); var sam = new Person('sam'); - this.assertEqual('-sam', Object.toJSON(sam)); - this.assertEqual('-sam', sam.toJSON()); + this.assertEqual('"-sam"', Object.toJSON(sam)); var element = $('test'); - this.assertUndefined(Object.toJSON(element)); element.toJSON = function(){return 'I\'m a div with id test'}; - this.assertEqual('I\'m a div with id test', Object.toJSON(element)); + this.assertEqual('"I\'m a div with id test"', Object.toJSON(element)); }, testObjectToHTML: function() { diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 6b910d93c..2851950f3 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -485,11 +485,6 @@ new Test.Unit.Runner({ }, 1000, 'previous: ');*/ }, - testToJSON: function() { - this.assertEqual('\"\"', ''.toJSON()); - this.assertEqual('\"test\"', 'test'.toJSON()); - }, - testIsJSON: function() { this.assert(!''.isJSON()); this.assert(!' '.isJSON()); @@ -526,10 +521,6 @@ new Test.Unit.Runner({ this.assertRaise('SyntaxError', function() { invalid.evalJSON() }); this.assertRaise('SyntaxError', function() { invalid.evalJSON(true) }); - attackTarget = "scared"; - dangerous.evalJSON(); - this.assertEqual("attack succeeded!", attackTarget); - attackTarget = "Not scared!"; this.assertRaise('SyntaxError', function(){dangerous.evalJSON(true)}); this.assertEqual("Not scared!", attackTarget); From 7eeee4f80f8834b3523be4bd709b8ce3ca1a2959 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 22 Feb 2010 17:28:24 +0100 Subject: [PATCH 175/502] Make Object.keys ES5 compliant. --- CHANGELOG | 2 ++ src/lang/object.js | 30 +++++++++++++++++++++++++++--- test/unit/object_test.js | 9 +++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 49e4b8302..36b072118 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Make Object.keys ES5 compliant. (Tobie Langel) + * Make Prototype's JSON implementation EcmaScript 5 compliant. [#453 state:resolved] (Tobie Langel) * Also detect embedded (UIWebView) mobile Safari. (Thomas Fuchs) diff --git a/src/lang/object.js b/src/lang/object.js index 64da68993..f8dfead9f 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -11,10 +11,30 @@ (function() { var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && typeof JSON.stringify === 'function' && JSON.stringify(0) === '0' && typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } /** * Object.extend(destination, source) -> Object @@ -170,9 +190,13 @@ * normalize the order of the object keys. **/ function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; - for (var property in object) - results.push(property); + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } return results; } @@ -315,7 +339,7 @@ toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, toQueryString: toQueryString, toHTML: toHTML, - keys: keys, + keys: Object.keys || keys, values: values, clone: clone, isElement: isElement, diff --git a/test/unit/object_test.js b/test/unit/object_test.js index e24080549..d44265dd8 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -23,6 +23,15 @@ new Test.Unit.Runner({ this.assertHashEqual({foo: 'foo'}, clone, "Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted."); }, + + testObjectKeys: function() { + this.assertEnumEqual([], Object.keys({})); + this.assertEnumEqual(['bar', 'foo'], Object.keys({foo: 'foo', bar: 'bar'}).sort()); + function Foo() { this.bar = 'bar'; } + Foo.prototype.foo = 'foo'; + this.assertEnumEqual(['bar'], Object.keys(new Foo())); + this.assertRaise('TypeError', function(){ Object.keys() }); + }, testObjectInspect: function() { this.assertEqual('undefined', Object.inspect()); From 2ec103d0973cbeeeb77e8fe0a08c750c4f63b6dd Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 22 Feb 2010 17:31:17 +0100 Subject: [PATCH 176/502] Clean-up JSON implementation for better ES5 compliance. --- src/lang/object.js | 107 ++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/src/lang/object.js b/src/lang/object.js index f8dfead9f..feca47f06 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -17,6 +17,10 @@ NUMBER_TYPE = 'Number', STRING_TYPE = 'String', OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && typeof JSON.stringify === 'function' && JSON.stringify(0) === '0' && @@ -90,54 +94,69 @@ * If there is one, it is used; otherwise the object is treated like a * generic `Object`. **/ - function toJSON(object) { - var results, type = typeof object; - - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); - case 'string': return object.inspect(true); - case 'number': return _numberToJSON(object); + + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); } - if (object === null) return 'null'; - - type = _toString.call(object); - - switch (type) { - case '[object Boolean]': return object.toString(); - case '[object String]': return object.inspect(true); - case '[object Number]': return _numberToJSON(object); - case '[object Array]': - results = []; - object.each(function(item) { - item = toJSON(item); - if (isUndefined(item)) item = 'null'; - results.push(item); - }); - return '[' + results.join(',') + ']'; + var _class = _toString.call(value); + + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); } - - if (typeof object.toJSON === 'function') - return toJSON(object.toJSON()); - if (isElement(object)) return; - results = []; - for (var property in object) { - var value = toJSON(object[property]); - if (!isUndefined(value)) - results.push(property.inspect(true) + ':' + value); + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; } - return '{' + results.join(',') + '}'; - } - - function _numberToJSON(number) { - return isFinite(number) ? String(number) : 'null'; + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } } - + function stringify(object) { return JSON.stringify(object); } @@ -272,7 +291,7 @@ * Returns `true` if `object` is an array; false otherwise. **/ function isArray(object) { - return _toString.call(object) == "[object Array]"; + return _toString.call(object) === ARRAY_CLASS; } var hasNativeIsArray = (typeof Array.isArray == 'function') @@ -310,7 +329,7 @@ * Returns `true` if `object` is of type `string`; `false` otherwise. **/ function isString(object) { - return _toString.call(object) == "[object String]"; + return _toString.call(object) === STRING_CLASS; } /** @@ -320,7 +339,7 @@ * Returns `true` if `object` is of type `number`; `false` otherwise. **/ function isNumber(object) { - return _toString.call(object) == "[object Number]"; + return _toString.call(object) === NUMBER_CLASS; } /** From 550ec6e053a42a1e4ed610c2cd2c543852120622 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 22 Feb 2010 18:41:37 +0100 Subject: [PATCH 177/502] Fix deprecated Selector.matchElements. --- CHANGELOG | 2 ++ src/deprecated.js | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 36b072118..9ce9c54ec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix deprecated Selector.matchElements. (Tobie Langel) + * Make Object.keys ES5 compliant. (Tobie Langel) * Make Prototype's JSON implementation EcmaScript 5 compliant. [#453 state:resolved] (Tobie Langel) diff --git a/src/deprecated.js b/src/deprecated.js index c008d66fd..4f4867790 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -241,7 +241,18 @@ Object.extend(Element.ClassNames.prototype, Enumerable); * * The only nodes returned will be those that match the given CSS selector. **/ - matchElements: Prototype.Selector.filter, + matchElements: function(elements, expression) { + var match = Prototype.Selector.match, + results = []; + + for (var i = 0, length = elements.length; i < length; i++) { + var element = elements[i]; + if (match(element, expression)) { + results.push(Element.extend(element)); + } + } + return results; + }, /** deprecated * Selector.findElement(elements, expression[, index = 0]) -> Element From e7761f13cce6bceec4ab086e1f183e1133558f4e Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 18 Feb 2010 23:06:26 -0800 Subject: [PATCH 178/502] doc: Merge/update old Form.serialize and serializeElements docs into source [#100 state:fixed_in_branch] --- src/dom/form.js | 76 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index d739817da..76d1fcbcd 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -51,7 +51,13 @@ var Form = { * the value for that key in the object will be an array of the field values * in the order they appeared on the array of elements. * - *
    The Options
    + * The preferred method to serialize a form is [[Form.serialize]]. Refer to + * it for further information and examples on the `hash` option. However, + * with [[Form.serializeElements]] you can serialize *specific* input + * elements of your choice, allowing you to specify a subset of form elements + * that you want to serialize data from. + * + * ##### The Options * * The options allow you to control two things: What kind of return * value you get (an object or a string), and whether and which `submit` @@ -61,26 +67,35 @@ var Form = { * `{ hash: false }` are used. * * If you supply an `options` object, it may have the following options: - * - `hash` ([[Boolean]]): `true` to return a plain object with keys and values - * (not a [[Hash]]; see below), `false` to return a String in query string - * format. If you supply an `options` object with no `hash` member, `hash` - * defaults to `true`. Note that this is __not__ the same as leaving off the - * `options` object entirely (see above). - * - `submit` ([[Boolean]] | [[String]]): In essence: If you omit this option the - * first submit button in the form is included; if you supply `false`, + * - `hash` ([[Boolean]]): `true` to return a plain object with keys and + * values (not a [[Hash]]; see below), `false` to return a String in query + * string format. If you supply an `options` object with no `hash` member, + * `hash` defaults to `true`. Note that this is __not__ the same as leaving + * off the `options` object entirely (see above). + * - `submit` ([[Boolean]] | [[String]]): In essence: If you omit this option + * the first submit button in the form is included; if you supply `false`, * no submit buttons are included; if you supply the name of a submit - * button, the first button with that name is included. Note that the `false` - * value __must__ really be `false`, not _falsey_; falsey-but-not-false is - * like omitting the option. + * button, the first button with that name is included. Note that the + * `false` value __must__ really be `false`, not _falsey_; + * falsey-but-not-false is like omitting the option. * - * _(Deprecated)_ If you pass in a [[Boolean]] instead of an object for `options`, it - * is used as the `hash` option and all other options are defaulted. + * _(Deprecated)_ If you pass in a [[Boolean]] instead of an object for + * `options`, it is used as the `hash` option and all other options are + * defaulted. * - *
    A hash, not a Hash
    + * ##### A hash, not a Hash * * If you opt to receive an object, it is a plain JavaScript object with keys - * and values, __not__ a [[Hash]]. All JavaScript objects are hashes in the lower-case - * sense of the word, which is why the option has that somewhat-confusing name. + * and values, __not__ a [[Hash]]. All JavaScript objects are hashes in the + * lower-case sense of the word, which is why the option has that + * somewhat-confusing name. + * + * ##### Examples + * + * To serialize all input elements of type "text": + * + * Form.serializeElements( $('myform').getInputs('text') ) + * // -> serialized data **/ serializeElements: function(elements, options) { // An earlier version accepted a boolean second parameter (hash) where @@ -117,9 +132,32 @@ Form.Methods = { * - options (Object): A list of options that affect the return value * of the method. * - * Serialize form data to an object or string suitable for Ajax requests. - * - * See [[Form.serializeElements]] for details on the options. + * Serializes form data to a string suitable for [[Ajax]] requests (default + * behavior) or, if the `hash` option evaluates to `true`, an object hash + * where keys are form control names and values are data. + * + * Depending of whether or not the `hash` option evaluates to `true`, the + * result is either an object of the form `{name: "johnny", color: "blue"}` + * or a [[String]] of the form `"name=johnny&color=blue"`, suitable for + * parameters in an [[Ajax]] request. This method mimics the way browsers + * serialize forms natively so that form data can be sent without refreshing + * the page. + * + * See [[Form.serializeElements]] for more details on the options. + * + * ##### Examples + * + * $('person-example').serialize() + * // -> 'username=sulien&age=22&hobbies=coding&hobbies=hiking' + * + * $('person-example').serialize(true) + * // -> {username: 'sulien', age: '22', hobbies: ['coding', 'hiking']} + * + * ##### Notes + * + * Disabled form elements are not serialized (as per W3C HTML recommendation). + * Also, file inputs are skipped as they cannot be serialized and sent using + * only JavaScript. **/ serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); From 557d1c86121bed3b248057ea37aeb78e0ae44292 Mon Sep 17 00:00:00 2001 From: dandean Date: Fri, 19 Feb 2010 00:02:18 -0800 Subject: [PATCH 179/502] doc: Merge/update various Form.Element function docs into source [#107 state:fixed_in_branch] --- src/dom/form.js | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/dom/form.js b/src/dom/form.js index 76d1fcbcd..0bc45691b 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -435,6 +435,13 @@ Form.Element = { * Form.Element.focus(element) -> Element * * Gives keyboard focus to an element. Returns the element. + * + * ##### Example + * + * Form.Element.focus('searchbox') + * + * // Almost equivalent, but does NOT return the form element (uses the native focus() method): + * $('searchbox').focus() **/ focus: function(element) { $(element).focus(); @@ -445,6 +452,22 @@ Form.Element = { * Form.Element.select(element) -> Element * * Selects the current text in a text input. Returns the element. + * + * ##### Example + * + * Some search boxes are set up so that they auto-select their content when they receive focus. + * + * $('searchbox').onfocus = function() { + * Form.Element.select(this) + * + * // You can also rely on the native method, but this will NOT return the element! + * this.select() + * } + * + * ##### Focusing + selecting: use [[Form.Element.activate]]! + * + * The [[Form.Element.activate]] method is a nifty way to both focus a form + * field and select its current text, all in one portable JavaScript call. **/ select: function(element) { $(element).select(); @@ -544,6 +567,20 @@ Form.Element.Methods = { * Form.Element.clear(@element) -> Element * * Clears the contents of a text input. Returns the element. + * + * ##### Example + * + * This code sets up a text field in a way that it clears its contents the + * first time it receives focus: + * + * $('some_field').onfocus = function() { + * // if already cleared, do nothing + * if (this._cleared) return + * + * // when this code is executed, "this" keyword will in fact be the field itself + * this.clear() + * this._cleared = true + * } **/ clear: function(element) { $(element).value = ''; @@ -604,6 +641,15 @@ Form.Element.Methods = { * * Gives focus to a form control and selects its contents if it is a text * input. + * + * This method is just a shortcut for focusing and selecting; therefore, + * these are equivalent (aside from the fact that the former one will __not__ + * return the field) : + * + * Form.Element.focus('myelement').select() + * $('myelement').activate() + * + * Guess which call is the nicest? ;) **/ activate: function(element) { element = $(element); @@ -621,6 +667,19 @@ Form.Element.Methods = { * * Disables a form control, effectively preventing its value from changing * until it is enabled again. + * + * This method sets the native `disabled` property of an element to `true`. + * You can use this property to check the state of a control. + * + * ##### Notes + * + * Disabled form controls are never serialized. + * + * Never disable a form control as a security measure without having + * validation for it server-side. A user with minimal experience of + * JavaScript can enable these fields on your site easily using any browser. + * Instead, use disabling as a usability enhancement - with it you can + * indicate that a specific value should not be changed at the time being. **/ disable: function(element) { element = $(element); From 0412f66a893810b48144618fd3bdb8b4d966702e Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 18 Feb 2010 23:42:29 -0800 Subject: [PATCH 180/502] doc: Merge/update miscellaneous Element.Methods docs into source [#100 state:fixed_in_branch] --- src/dom/dom.js | 144 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 2 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index cd62b0af9..b26c57525 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -236,7 +236,7 @@ Element.Methods = { * } * * - * […] + * [...] * *
    * @@ -251,6 +251,46 @@ Element.Methods = { * Element.toggle(@element) -> Element * * Toggles the visibility of `element`. Returns `element`. + * + * ##### Examples + * + *
    + * + * + * $('welcome-message').toggle(); + * // -> Element (and hides div#welcome-message) + * + * $('error-message').toggle(); + * // -> Element (and displays div#error-message) + * + * Toggle multiple elements using [[Enumerable#each]]: + * + * ['error-message', 'welcome-message'].each(Element.toggle); + * // -> ['error-message', 'welcome-message'] + * + * Toggle multiple elements using [[Enumerable#invoke]]: + * + * $('error-message', 'welcome-message').invoke('toggle'); + * // -> [Element, Element] + * + * ##### Notes + * + * [[Element.toggle]] _cannot_ display elements hidden via CSS stylesheets. + * Note that this is not a Prototype limitation but a consequence of how the + * CSS `display` property works. + * + * + * + * [...] + * + *
    + * + * $('hidden-by-css').toggle(); // WONT' WORK! + * // -> Element (div#hidden-by-css is still hidden!) **/ toggle: function(element) { element = $(element); @@ -258,11 +298,29 @@ Element.Methods = { return element; }, - /** * Element.hide(@element) -> Element * * Sets `display: none` on `element`. Returns `element`. + * + * ##### Examples + * + * Hide a single element: + * + *
    + * + * $('error-message').hide(); + * // -> Element (and hides div#error-message) + * + * Hide multiple elements using [[Enumerable#each]]: + * + * ['content', 'navigation', 'footer'].each(Element.hide); + * // -> ['content', 'navigation', 'footer'] + * + * Hide multiple elements using [[Enumerable#invoke]]: + * + * $('content', 'navigation', 'footer').invoke('hide'); + * // -> [Element, Element, Element] **/ hide: function(element) { element = $(element); @@ -274,6 +332,44 @@ Element.Methods = { * Element.show(@element) -> Element * * Removes `display: none` on `element`. Returns `element`. + * + * ##### Examples + * + * Show a single element: + * + * + * + * $('error-message').show(); + * // -> Element (and displays div#error-message) + * + * Show multiple elements using [[Enumerable#each]]: + * + * ['content', 'navigation', 'footer'].each(Element.show); + * // -> ['content', 'navigation', 'footer'] + * + * Show multiple elements using [[Enumerable#invoke]]: + * + * $('content', 'navigation', 'footer').invoke('show'); + * // -> [Element, Element, Element] + * + * ##### Notes + * + * [[Element.show]] _cannot_ display elements hidden via CSS stylesheets. + * Note that this is not a Prototype limitation but a consequence of how the + * CSS `display` property works. + * + * + * + * [...] + * + *
    + * + * $('hidden-by-css').show(); // DOES NOT WORK! + * // -> Element (div#error-message is still hidden!) **/ show: function(element) { element = $(element); @@ -1583,6 +1679,30 @@ Element.Methods = { * * Returns `element`'s ID. If `element` does not have an ID, one is * generated, assigned to `element`, and returned. + * + * ##### Examples + * + * Original HTML: + * + *
      + *
    • apple
    • + *
    • orange
    • + *
    + * + * JavaScript: + * + * $('apple').identify(); + * // -> 'apple' + * + * $('apple').next().identify(); + * // -> 'anonymous_element_1' + * + * Resulting HTML: + * + *
      + *
    • apple
    • + *
    • orange
    • + *
    **/ identify: function(element) { element = $(element); @@ -1877,6 +1997,17 @@ Element.Methods = { * Element.empty(@element) -> Element * * Tests whether `element` is empty (i.e., contains only whitespace). + * + * ##### Examples + * + *
    + *
    full!
    + * + * $('wallet').empty(); + * // -> true + * + * $('cart').empty(); + * // -> false **/ empty: function(element) { return $(element).innerHTML.blank(); @@ -1926,6 +2057,15 @@ Element.Methods = { * Element.scrollTo(@element) -> Element * * Scrolls the window so that `element` appears at the top of the viewport. + * + * This has a similar effect than what would be achieved using + * [HTML anchors](http://www.w3.org/TR/html401/struct/links.html#h-12.2.3) + * (except the browser's history is not modified). + * + * ##### Example + * + * $(element).scrollTo(); + * // -> Element **/ scrollTo: function(element) { element = $(element); From ae03e319674941a4c07531415827ff589a43399a Mon Sep 17 00:00:00 2001 From: Tim Walker Date: Sat, 20 Feb 2010 13:54:40 -0800 Subject: [PATCH 181/502] doc: Merge/update old Event overview docs into source [#93 state:fixed_in_branch] --- src/dom/event.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index d06fa48a9..8571789b7 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -5,7 +5,7 @@ * * The namespace for Prototype's event system. * - *
    Events: a fine mess
    + * ##### Events: a fine mess * * Event management is one of the really sore spots of cross-browser * scripting. @@ -17,7 +17,7 @@ * Safari). Also, MSIE has a tendency to leak memory when it comes to * discarding event handlers. * - *
    Prototype to the rescue
    + * ##### Prototype to the rescue * * Of course, Prototype smooths it over so well you'll forget these * troubles even exist. Enter the `Event` namespace. It is replete with @@ -33,6 +33,35 @@ * The functions you're most likely to use a lot are [[Event.observe]], * [[Event.element]] and [[Event.stop]]. If your web app uses custom events, * you'll also get a lot of mileage out of [[Event.fire]]. + * + * ##### Instance methods on event objects + * As of Prototype 1.6, all methods on the `Event` object are now also + * available as instance methods on the event object itself: + * + * **Before** + * + * $('foo').observe('click', respondToClick); + * + * function respondToClick(event) { + * var element = Event.element(event); + * element.addClassName('active'); + * } + * + * **After** + * + * $('foo').observe('click', respondToClick); + * + * function respondToClick(event) { + * var element = event.element(); + * element.addClassName('active'); + * } + * + * These methods are added to the event object through [[Event.extend]], + * in the same way that `Element` methods are added to DOM nodes through + * [[Element.extend]]. Events are extended automatically when handlers are + * registered with Prototype's [[Event.observe]] method; if you're using a + * different method of event registration, for whatever reason,you'll need to + * extend these events manually with [[Event.extend]]. **/ var Event = { KEY_BACKSPACE: 8, @@ -351,6 +380,18 @@ inspect: function() { return '[object Event]' } }); + /** + * Event.extend(@event) -> Event + * + * Extends `event` with all of the methods contained in `Event.Methods`. + * + * Note that all events inside handlers that were registered using + * [[Event.observe]] or [[Element.observe]] will be extended automatically. + * + * You need only call `Event.extend` manually if you register a handler a + * different way (e.g., the `onclick` attribute). We really can't encourage + * that sort of thing, though. + **/ // IE's method for extending events. Event.extend = function(event, element) { if (!event) return false; From 64dbb2a4285865622c3188f69a692e83dc3caf9d Mon Sep 17 00:00:00 2001 From: dandean Date: Sun, 21 Feb 2010 13:30:28 -0800 Subject: [PATCH 182/502] doc: Merge/update old Ajax.Request docs into source [#67 state:fixed_in_branch] --- src/ajax.js | 2 +- src/ajax/request.js | 103 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/ajax.js b/src/ajax.js index f828e9943..412d24849 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -15,7 +15,7 @@ * * * `X-Requested-With` is set to `XMLHttpRequest`. * * `X-Prototype-Version` is set to Prototype's current version (e.g., - * `1.6.0.3`). + * `<%= PROTOTYPE_VERSION %>`). * * `Accept` is set to `text/javascript, text/html, application/xml, * text/xml, * / *` * * `Content-type` is automatically determined based on the `contentType` diff --git a/src/ajax/request.js b/src/ajax/request.js index 352cc4809..b59f3cf0e 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -3,9 +3,92 @@ * * Initiates and processes an Ajax request. * - * `Ajax.Request` is a general-purpose class for making HTTP requests. + * [[Ajax.Request]] is a general-purpose class for making HTTP requests which + * handles the life-cycle of the request, handles the boilerplate, and lets + * you plug in callback functions for your custom needs. * - *
    Automatic JavaScript response evaluation
    + * In the optional `options` hash, you usually provide an `onComplete` and/or + * `onSuccess` callback, unless you're in the edge case where you're getting a + * JavaScript-typed response, that will automatically be `eval`'d. + * + * For a full list of common options and callbacks, see "Ajax options" heading + * of the [[Ajax section]]. + * + * ##### A basic example + * + * new Ajax.Request('/your/url', { + * onSuccess: function(response) { + * // Handle the response content... + * } + * }); + * + * ##### Request life-cycle + * + * Underneath our nice requester objects lies, of course, `XMLHttpRequest`. The + * defined life-cycle is as follows: + * + * 1. Created + * 2. Initialized + * 3. Request sent + * 4. Response being received (can occur many times, as packets come in) + * 5. Response received, request complete + * + * As you can see under the "Ajax options" heading of the [[Ajax section]], + * Prototype's AJAX objects define a whole slew of callbacks, which are + * triggered in the following order: + * + * 1. `onCreate` (this is actually a callback reserved to [[Ajax.Responders]]) + * 2. `onUninitialized` (maps on Created) + * 3. `onLoading` (maps on Initialized) + * 4. `onLoaded` (maps on Request sent) + * 5. `onInteractive` (maps on Response being received) + * 6. `on`*XYZ* (numerical response status code), onSuccess or onFailure (see below) + * 7. `onComplete` + * + * The two last steps both map on *Response received*, in that order. If a + * status-specific callback is defined, it gets invoked. Otherwise, if + * `onSuccess` is defined and the response is deemed a success (see below), it + * is invoked. Otherwise, if `onFailure` is defined and the response is *not* + * deemed a sucess, it is invoked. Only after that potential first callback is + * `onComplete` called. + * + * ##### A note on portability + * + * Depending on how your browser implements `XMLHttpRequest`, one or more + * callbacks may never be invoked. In particular, `onLoaded` and + * `onInteractive` are not a 100% safe bet so far. However, the global + * `onCreate`, `onUninitialized` and the two final steps are very much + * guaranteed. + * + * ##### `onSuccess` and `onFailure`, the under-used callbacks + * + * Way too many people use [[Ajax.Request]] in a similar manner to raw XHR, + * defining only an `onComplete` callback even when they're only interested in + * "successful" responses, thereby testing it by hand: + * + * // This is too bad, there's better! + * new Ajax.Request('/your/url', { + * onComplete: function(response) { + * if (200 == response.status) + * // yada yada yada + * } + * }); + * + * First, as described below, you could use better "success" detection: success + * is generally defined, HTTP-wise, as either no response status or a "2xy" + * response status (e.g., 201 is a success, too). See the example below. + * + * Second, you could dispense with status testing altogether! Prototype adds + * callbacks specific to success and failure, which we listed above. Here's + * what you could do if you're only interested in success, for instance: + * + * new Ajax.Request('/your/url', { + * onSuccess: function(response) { + * // yada yada yada + * } + * }); + * + * ##### Automatic JavaScript response evaluation * * If an Ajax request follows the _same-origin policy_ **and** its response * has a JavaScript-related `Content-type`, the content of the `responseText` @@ -28,12 +111,12 @@ * * The MIME-type string is examined in a case-insensitive manner. * - *
    Methods you may find useful
    + * ##### Methods you may find useful * - * Instances of the `Request` object provide several methods that can come in - * handy in your callback functions, especially once the request is complete. + * Instances of the [[Ajax.Request]] object provide several methods that come + * in handy in your callback functions, especially once the request is complete. * - *
    Is the response a successful one?
    + * ###### Is the response a successful one? * * The [[Ajax.Request#success]] method examines the XHR object's `status` * property and follows general HTTP guidelines: unknown status is deemed @@ -41,7 +124,7 @@ * better way of testing your response than the usual * `200 == transport.status`. * - *
    Getting HTTP response headers
    + * ###### Getting HTTP response headers * * While you can obtain response headers from the XHR object using its * `getResponseHeader` method, this makes for verbose code, and several @@ -58,14 +141,14 @@ * } * }); * - *
    Evaluating JSON headers
    + * ##### Evaluating JSON headers * * Some backends will return JSON not as response text, but in the `X-JSON` * header. In this case, you don't even need to evaluate the returned JSON * yourself, as Prototype automatically does so. It passes the result as the * `headerJSON` property of the [[Ajax.Response]] object. Note that if there - * is no such header — or its contents are invalid — `headerJSON` will be set - * to `null`. + * is no such header — or its contents are invalid — `headerJSON` + * will be set to `null`. * * new Ajax.Request('/your/url', { * onSuccess: function(transport) { From f484b5e896d1d1dd2408e01b8407bddafa4fe5e0 Mon Sep 17 00:00:00 2001 From: dandean Date: Sun, 21 Feb 2010 15:25:49 -0800 Subject: [PATCH 183/502] doc: Merge/updated old Ajax.Response docs into source [#68 state:fixed_in_branch] --- src/ajax/response.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ajax/response.js b/src/ajax/response.js index 18cd8ba80..5054909b1 100644 --- a/src/ajax/response.js +++ b/src/ajax/response.js @@ -4,18 +4,21 @@ * A wrapper class around `XmlHttpRequest` for dealing with HTTP responses * of Ajax requests. * - * An instance of `Ajax.Response` is passed as the first argument of all Ajax + * An instance of [[Ajax.Response]] is passed as the first argument of all Ajax * requests' callbacks. You _will not_ need to create instances of - * `Ajax.Response` yourself. + * [[Ajax.Response]] yourself. **/ /** * Ajax.Response#readyState -> Number * - * The request's current state. + * A [[Number]] corresponding to the request's current state. * - * `0` corresponds to `"Uninitialized"`, `1` to `"Loading"`, `2` to - * `"Loaded"`, `3` to `"Interactive"`, and `4` to `"Complete"`. + * `0` : `"Uninitialized"`
    + * `1` : `"Loading"`
    + * `2` : `"Loaded"`
    + * `3` : `"Interactive"`
    + * `4` : `"Complete"` **/ /** @@ -42,6 +45,7 @@ * Ajax.Response#headerJSON -> Object | Array | null * * Auto-evaluated content of the `X-JSON` header if present; `null` otherwise. + * This is useful to transfer _small_ amounts of data. **/ /** @@ -110,8 +114,8 @@ Ajax.Response = Class.create({ /** * Ajax.Response#getAllHeaders() -> String | null * - * Returns a string containing all headers separated by line breaks. _Does - * not__ throw errors if no headers are present the way its native + * Returns a [[String]] containing all headers separated by line breaks. + * _Does not_ throw errors if no headers are present the way its native * counterpart does. **/ getAllHeaders: function() { @@ -125,7 +129,8 @@ Ajax.Response = Class.create({ * * Returns the value of the requested header if present; throws an error * otherwise. This is just a wrapper around the `XmlHttpRequest` method of - * the same name. + * the same name. Prefer it's shorter counterpart: + * [[Ajax.Response#getHeader]]. **/ getResponseHeader: function(name) { return this.transport.getResponseHeader(name); @@ -134,9 +139,10 @@ Ajax.Response = Class.create({ /** * Ajax.Response#getAllResponseHeaders() -> String * - * Returns a string containing all headers separated by line breaks; throws + * Returns a [[String]] containing all headers separated by line breaks; throws * an error if no headers exist. This is just a wrapper around the - * `XmlHttpRequest` method of the same name. + * `XmlHttpRequest` method of the same name. Prefer it's shorter counterpart: + * [[Ajax.Response#getAllHeaders]]. **/ getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); From ab1663aa6d4335480337f2f16d9ba4bec07dc2db Mon Sep 17 00:00:00 2001 From: dandean Date: Sun, 21 Feb 2010 17:49:57 -0800 Subject: [PATCH 184/502] doc: Merge/update old docs for Elements.Methods offset/positioning functions into source [#78 state:fixed_in_branch] --- src/dom/dom.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index b26c57525..27cbc614f 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2286,6 +2286,9 @@ Element.Methods = { * Allows for the easy creation of a CSS containing block by setting * `element`'s CSS `position` to `relative` if its initial position is * either `static` or `undefined`. + * + * To revert back to `element`'s original CSS position, use + * [[Element.undoPositioned]]. **/ makePositioned: function(element) { element = $(element); @@ -2308,6 +2311,10 @@ Element.Methods = { * * Sets `element` back to the state it was in _before_ * [[Element.makePositioned]] was applied to it. + * + * `element`'s absolutely positioned children will now have their positions + * set relatively to `element`'s nearest ancestor with a CSS `position` of + * `'absolute'`, `'relative'` or `'fixed'`. **/ undoPositioned: function(element) { element = $(element); @@ -2467,6 +2474,13 @@ Element.Methods = { * * Returns an array in the form of `[leftValue, topValue]`. Also accessible * as properties: `{ left: leftValue, top: topValue }`. + * + * Calculates the cumulative `offsetLeft` and `offsetTop` of an element and + * all its parents _until_ it reaches an element with a position other than + * `static`. + * + * Note that all values are returned as _numbers only_ although they are + * _expressed in pixels_. **/ positionedOffset: function(element) { var valueT = 0, valueL = 0; @@ -2573,6 +2587,9 @@ Element.Methods = { * * Returns `element`'s closest _positioned_ ancestor. If none is found, the * `body` element is returned. + * + * The returned element is `element`'s + * [CSS containing block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details). **/ getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); From f948220767b7a0a3f3ab6f3b63a43f8b9f48855b Mon Sep 17 00:00:00 2001 From: Samuel Lebeau Date: Tue, 2 Mar 2010 02:43:04 +0100 Subject: [PATCH 185/502] doc: Merge/update old docs for $$ into source [#112 state:fixed_in_branch] --- src/dom/selector.js | 87 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index 0804337e9..78cb90fba 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,9 +1,88 @@ /** section: DOM, related to: Prototype.Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. + * $$(cssRule...) -> [Element...] + * + * Takes an arbitrary number of CSS selectors (strings) and returns a document-order + * array of extended DOM elements that match any of them. + * + * Sometimes the usual tools from your DOM arsenal -- `document.getElementById` encapsulated + * by [[$]], `getElementsByTagName` and even Prototype's very own `getElementsByClassName` + * extensions -- just aren't enough to quickly find elements or collections of elements. + * If you know the DOM tree structure, you can simply resort to CSS selectors to get + * the job done. + * + * ##### Quick examples + * + * $$('div'); + * // -> all DIVs in the document. Same as document.getElementsByTagName('div'). + * // Nice addition, the elements you're getting back are already extended! + * + * $$('#contents'); + * // -> same as $('contents'), only it returns an array anyway (even though IDs must + * // be unique within a document). + * + * $$('li.faux'); + * // -> all LI elements with class 'faux' + * + * The [[$$]] function searches the entire document. For selector queries on more specific + * sections of a document, use [[Element.select]]. + * + * ##### Supported CSS syntax + * + * The [[$$]] function does not rely on the browser's internal CSS parsing capabilities + * (otherwise, we'd be in cross-browser trouble...), and therefore offers a consistent + * set of selectors across all supported browsers. + * + * ###### Supported in v1.5.0 + * + * * Type selector: tag names, as in `div`. + * * Descendant selector: the space(s) between other selectors, as in `#a li`. + * * Attribute selectors: the full CSS 2.1 set of `[attr]`, `[attr=value]`, `[attr~=value]` + * and `[attr|=value]`. It also supports `[attr!=value]`. If the value you're matching + * against includes a space, be sure to enclose the value in quotation marks (`[title="Hello World!"]`). + * * Class selector: CSS class names, as in `.highlighted` or `.example.wrong`. + * * ID selector: as in `#item1`. + * + * ###### Supported from v1.5.1 + * + * Virtually all of [CSS3](http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#selectors) + * is supported, with the exception of pseudo-elements (like `::first-letter`) and some + * pseudo-classes (like `:hover`). Some examples of new selectors that can be used in 1.5.1: + * + * * Child selector: selects immediate descendants, as in `#a > li`. + * * Attribute selectors: all attribute operators are supported, including `~=` (matches + * part of a space-delimited attribute value, like `rel` or `class`); `^=` (matches the + * beginning of a value); `$=` (matches the end of a value); and `*=` (matches any part + * of the value). + * * The `:not` pseudo-class, as in `#a *:not(li)` (matches all descendants of `#a` that + * aren't LIs). + * * All the `:nth`, `:first`, and `:last` pseudo-classes. Examples include `tr:nth-child(even)` + * (all even table rows), `li:first-child` (the first item in any list), or `p:nth-last-of-type(3)` + * (the third-to-last paragraph on the page). + * * The `:empty` pseudo-class (for selecting elements without children or text content). + * * The `:enabled`, `:disabled`, and `:checked` pseudo-classes (for use with form controls). + * + * ##### Examples + * + * $$('#contents a[rel]'); + * // -> all links inside the element of ID "contents" with a rel attribute + * + * $$('a[href="#"]'); + * // -> all links with a href attribute of value "#" (eyeew!) + * + * $$('#navbar a', '#sidebar a'); + * // -> all links within the elements of ID "navbar" or "sidebar" + * + * **With version 1.5.1 and above** you can do various types of advanced selectors: + * + * $$('a:not([rel~=nofollow])'); + * // -> all links, excluding those whose rel attribute contains the word "nofollow" + * + * $$('table tbody > tr:nth-child(even)'); + * // -> all even rows within all table bodies + * + * $$('div:empty'); + * // -> all DIVs without content (i.e., whitespace-only) **/ - window.$$ = function() { var expression = $A(arguments).join(', '); return Prototype.Selector.select(expression, document); From 8a7be689729c963bae784a90989fb02cb8edcb3c Mon Sep 17 00:00:00 2001 From: dandean Date: Thu, 4 Mar 2010 13:28:48 -0800 Subject: [PATCH 186/502] doc: Merge/update old Prototype namespace docs into source [#123 state:fixed_in_branch] --- src/dom.js | 7 --- src/lang/class.js | 2 +- src/prototype.js | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/src/dom.js b/src/dom.js index 1fb0187d1..f38cf2cfa 100644 --- a/src/dom.js +++ b/src/dom.js @@ -19,13 +19,6 @@ * **/ -/** section: DOM - * Prototype - * - * The Prototype namespace. - * -**/ - //= require "dom/dom" //= require //= require "dom/selector" diff --git a/src/lang/class.js b/src/lang/class.js index b277e2bbc..7231d9423 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -1,7 +1,7 @@ /* Based on Alex Arnell's inheritance implementation. */ /** section: Language - * Class + * class Class * * Manages Prototype's class-based OOP system. * diff --git a/src/prototype.js b/src/prototype.js index 5f78f7824..3d6d1d9b2 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -6,9 +6,72 @@ * *--------------------------------------------------------------------------*/ +/** section: Language + * Prototype + * + * The [[Prototype]] namespace provides fundamental information about the + * Prototype library you're using, as well as a central repository for default + * iterators or functions. + * + * We say "namespace," because the [[Prototype]] object is not intended for + * instantiation, nor for mixing in other objects. It's really just... a + * namespace. + * + * ##### Your version of Prototype + * + * Your scripts can check against a particular version of Prototype by + * examining [[Prototype.Version]], which is a version [[String]] (e.g. + * "<%= PROTOTYPE_VERSION %>"). The famous + * [script.aculo.us](http://script.aculo.us) library does this at load time to + * ensure it's being used with a reasonably recent version of Prototype, for + * instance. + * + * ##### Browser features + * + * Prototype also provides a (nascent) repository of + * [[Prototype.BrowserFeatures browser feature information]], which it then + * uses here and there in its source code. The idea is, first, to make + * Prototype's source code more readable; and second, to centralize whatever + * scripting trickery might be necessary to detect the browser feature, in + * order to ease maintenance. + * + * ##### Default iterators and functions + * + * Numerous methods in Prototype objects (most notably the [[Enumerable]] + * module) let the user pass in a custom iterator, but make it optional by + * defaulting to an "identity function" (an iterator that just returns its + * argument, untouched). This is the [[Prototype.K]] function, which you'll + * see referred to in many places. + * + * Many methods also take it easy by protecting themselves against missing + * methods here and there, reverting to empty functions when a supposedly + * available method is missing. Such a function simply ignores its potential + * arguments, and does nothing whatsoever (which is, oddly enough, + * blazing fast). The quintessential empty function sits, unsurprisingly, + * at [[Prototype.emptyFunction]] (note the lowercase first letter). +**/ var Prototype = { + + /** + * Prototype.Version -> String + * + * The version of the Prototype library you are using (e.g. + * "<%= PROTOTYPE_VERSION %>"). + **/ Version: '<%= PROTOTYPE_VERSION %>', + /** + * Prototype.Browser + * + * A collection of [[Boolean]] values indicating the browser which is + * currently in use. Available properties are `IE`, `Opera`, `WebKit`, + * `MobileSafari` and `Gecko`. + * + * Example + * + * Prototype.Browser.WebKit; + * //-> true, when executed in any WebKit-based browser. + **/ Browser: (function(){ var ua = navigator.userAgent; // Opera (at least) 8.x+ has "Opera" as a [[Class]] of `window.opera` @@ -23,9 +86,35 @@ var Prototype = { } })(), + /** + * Prototype.BrowserFeatures + * + * A collection of [[Boolean]] values indicating the presence of specific + * browser features. + **/ BrowserFeatures: { + /** + * Prototype.BrowserFeatures.XPath -> Boolean + * + * Used internally to detect if the browser supports + * [DOM Level 3 XPath](http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html). + **/ XPath: !!document.evaluate, + + /** + * Prototype.BrowserFeatures.SelectorsAPI -> Boolean + * + * Used internally to detect if the browser supports the + * [NodeSelector API](http://www.w3.org/TR/selectors-api/#nodeselector). + **/ SelectorsAPI: !!document.querySelector, + + /** + * Prototype.BrowserFeatures.ElementExtensions -> Boolean + * + * Used internally to detect if the browser supports extending html element + * prototypes. + **/ ElementExtensions: (function() { var constructor = window.Element || window.HTMLElement; return !!(constructor && constructor.prototype); @@ -52,7 +141,40 @@ var Prototype = { ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + /** + * Prototype.emptyFunction([argument...]) -> undefined + * - argument (Object): Optional arguments + * + * The [[Prototype.emptyFunction]] does nothing... and returns nothing! + * + * It is used thoughout the framework to provide a fallback function in order + * to cut down on conditionals. Typically you'll find it as a default value + * for optional callback functions. + **/ emptyFunction: function() { }, + + /** + * Prototype.K(argument) -> argument + * - argument (Object): Optional argument... + * + * [[Prototype.K]] is Prototype's very own + * [identity function](http://en.wikipedia.org/wiki/Identity_function), i.e. + * it returns its `argument` untouched. + * + * This is used throughout the framework, most notably in the [[Enumerable]] + * module as a default value for iterators. + * + * ##### Examples + * + * Prototype.K('hello world!'); + * // -> 'hello world!' + * + * Prototype.K(200); + * // -> 200 + * + * Prototype.K(Prototype.K); + * // -> Prototype.K + **/ K: function(x) { return x } }; From 67124ebee48798b7f86a9d4fcc4e4fe8394136fd Mon Sep 17 00:00:00 2001 From: dandean Date: Sun, 21 Feb 2010 19:43:10 -0800 Subject: [PATCH 187/502] doc: Cleanup and normalize PDoc source --- src/ajax.js | 12 +-- src/ajax/periodical_updater.js | 34 +++--- src/ajax/updater.js | 30 +++--- src/dom/dom.js | 185 ++++++++++++++++++--------------- src/dom/event.js | 20 ++-- src/dom/form.js | 30 +++--- src/lang.js | 8 +- src/lang/array.js | 77 +++++++------- src/lang/class.js | 12 +-- src/lang/date.js | 2 +- src/lang/enumerable.js | 72 ++++++------- src/lang/function.js | 20 ++-- src/lang/hash.js | 48 ++++----- 13 files changed, 288 insertions(+), 262 deletions(-) diff --git a/src/ajax.js b/src/ajax.js index 412d24849..b246aa854 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -3,12 +3,12 @@ * * Prototype's APIs around the `XmlHttpRequest` object. * - * The Prototype framework enables you to deal with Ajax calls in a manner that is - * both easy and compatible with all modern browsers. + * The Prototype framework enables you to deal with Ajax calls in a manner that + * is both easy and compatible with all modern browsers. * * Actual requests are made by creating instances of [[Ajax.Request]]. * - *
    Request headers
    + * ##### Request headers * * The following headers are sent with all Ajax requests (and can be * overridden with the `requestHeaders` option described below): @@ -21,13 +21,13 @@ * * `Content-type` is automatically determined based on the `contentType` * and `encoding` options. * - *
    Ajax options
    + * ##### Ajax options * * All Ajax classes share a common set of _options_ and _callbacks_. * Callbacks are called at various points in the life-cycle of a request, and * always feature the same list of arguments. * - *
    Common options
    + * ##### Common options * * * `asynchronous` ([[Boolean]]; default `true`): Determines whether * `XMLHttpRequest` is used asynchronously or not. Synchronous usage is @@ -64,7 +64,7 @@ * `true` otherwise): Sanitizes the contents of * [[Ajax.Response#responseText]] before evaluating it. * - *
    Common callbacks
    + * ##### Common callbacks * * When used on individual instances, all callbacks (except `onException`) are * invoked with two parameters: the [[Ajax.Response]] object and the result of diff --git a/src/ajax/periodical_updater.js b/src/ajax/periodical_updater.js index 0cbb41d9a..5d2df01f9 100644 --- a/src/ajax/periodical_updater.js +++ b/src/ajax/periodical_updater.js @@ -4,9 +4,9 @@ * Periodically performs an Ajax request and updates a container's contents * based on the response text. * - * `Ajax.PeriodicalUpdater` behaves like [[Ajax.Updater]], but performs the + * [[Ajax.PeriodicalUpdater]] behaves like [[Ajax.Updater]], but performs the * update at a prescribed interval, rather than only once. (Note that it is - * _not_ a subclass of `Ajax.Updater`; it's a wrapper around it.) + * _not_ a subclass of [[Ajax.Updater]]; it's a wrapper around it.) * * This class addresses the common need of periodical update, as required by * all sorts of "polling" mechanisms (e.g., an online chatroom or an online @@ -16,10 +16,11 @@ * keeping track of the response text so it can (optionally) react to * receiving the exact same response consecutively. * - *
    Additional options
    + * ##### Additional options * - * `Ajax.PeriodicalUpdater` features all the common options and callbacks - * described in the [[Ajax section]] — _plus_ those added by `Ajax.Updater`. + * [[Ajax.PeriodicalUpdater]] features all the common options and callbacks + * described in the [[Ajax section]] — _plus_ those added by + * [[Ajax.Updater]]. * * It also provides two new options: * @@ -34,9 +35,9 @@ * is the same; when the result is different once again, `frequency` will * revert to its original value. * - *
    Disabling and re-enabling a PeriodicalUpdater
    + * ##### Disabling and re-enabling a [[Ajax.PeriodicalUpdater]] * - * You can hit the brakes on a running `PeriodicalUpdater` by calling + * You can hit the brakes on a running [[Ajax.PeriodicalUpdater]] by calling * [[Ajax.PeriodicalUpdater#stop]]. If you wish to re-enable it later, call * [[Ajax.PeriodicalUpdater#start]]. * @@ -55,7 +56,7 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { * - options (Object): Configuration for the request. See the * [[Ajax section]] for more information. * - * Creates a new `Ajax.PeriodicalUpdater`. + * Creates a new [[Ajax.PeriodicalUpdater]]. * * Periodically performs an AJAX request and updates a container's contents * based on the response text. Offers a mechanism for "decay," which lets it @@ -71,7 +72,7 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { * * ##### Additional options * - * `Ajax.PeriodicalUpdater` features all the common options and callbacks + * [[Ajax.PeriodicalUpdater]] features all the common options and callbacks * (see the [[Ajax section]] for more information), plus those added by * [[Ajax.Updater]]. It also provides two new options that deal with the * original period, and its decay rate (how Rocket Scientist does that make @@ -111,7 +112,8 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { * *
    * - * To better understand decay, here is a small sequence of calls from the following example: + * To better understand decay, here is a small sequence of calls from the + * following example: * * new Ajax.PeriodicalUpdater('items', '/items', { * method: 'get', frequency: 3, decay: 2 @@ -188,17 +190,17 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { * * * - * ##### Disabling and re-enabling a `PeriodicalUpdater` + * ##### Disabling and re-enabling a [[Ajax.PeriodicalUpdater]] * - * You can pull the brake on a running `PeriodicalUpdater` by simply calling - * its `stop` method. If you wish to re-enable it later, just call its `start` - * method. Both take no argument. + * You can pull the brake on a running [[Ajax.PeriodicalUpdater]] by simply + * calling its `stop` method. If you wish to re-enable it later, just call + * its `start` method. Both take no argument. * * ##### Beware! Not a specialization! * - * `Ajax.PeriodicalUpdater` is not a specialization of [[Ajax.Updater]], + * [[Ajax.PeriodicalUpdater]] is not a specialization of [[Ajax.Updater]], * despite its name. When using it, do not expect to be able to use methods - * normally provided by [[Ajax.Request]] and "inherited" by `Ajax.Updater`, + * normally provided by [[Ajax.Request]] and "inherited" by [[Ajax.Updater]], * such as `evalJSON` or `getHeader`. Also the `onComplete` callback is * hijacked to be used for update management, so if you wish to be notified * of every successful request, use `onSuccess` instead (beware: it will get diff --git a/src/ajax/updater.js b/src/ajax/updater.js index 7cb23da86..b69b78f79 100644 --- a/src/ajax/updater.js +++ b/src/ajax/updater.js @@ -4,10 +4,10 @@ * A class that performs an Ajax request and updates a container's contents * with the contents of the response. * - * `Ajax.Updater` is a subclass of [[Ajax.Request]] built for a common + * [[Ajax.Updater]] is a subclass of [[Ajax.Request]] built for a common * use-case. * - *
    Example
    + * ##### Example * * new Ajax.Updater('items', '/items', { * parameters: { text: $F('text') } @@ -17,15 +17,15 @@ * parameters); it will then replace the contents of the element with the ID * of `items` with whatever response it receives. * - *
    Callbacks
    + * ##### Callbacks * - * `Ajax.Updater` supports all the callbacks listed in the [[Ajax section]]. + * [[Ajax.Updater]] supports all the callbacks listed in the [[Ajax section]]. * Note that the `onComplete` callback will be invoked **after** the element * is updated. * - *
    Additional options
    + * ##### Additional options * - * `Ajax.Updater` has some options of its own apart from the common options + * [[Ajax.Updater]] has some options of its own apart from the common options * described in the [[Ajax section]]: * * * `evalScripts` ([[Boolean]]; defaults to `false`): Whether `'); - * // -> HTMLElement (and prints "updated!" in an alert dialog). + * // -> Element (and prints "updated!" in an alert dialog). * $('fruits').innerHTML; * // -> '

    Kiwi, banana and apple.

    ' * * Relying on the `toString()` method: * * $('fruits').update(123); - * // -> HTMLElement + * // -> Element * $('fruits').innerHTML; * // -> '123' * @@ -602,6 +614,7 @@ Element.Methods = { * * ##### Examples * + * language: html *
    *
    *

    Kiwi, banana and apple.

    @@ -611,7 +624,7 @@ Element.Methods = { * Passing an HTML snippet: * * $('first').replace('
    • kiwi
    • banana
    • apple
    '); - * // -> HTMLElement (p#first) + * // -> Element (p#first) * * $('fruits').innerHTML; * // -> '
    • kiwi
    • banana
    • apple
    ' @@ -619,23 +632,23 @@ Element.Methods = { * Again, with a `'); - * // -> HTMLElement (ul#favorite) and prints "removed!" in an alert dialog. + * // -> Element (ul#favorite) and prints "removed!" in an alert dialog. * - * $('fruits').innerHTML + * $('fruits').innerHTML; * // -> '

    Melon, oranges and grapes.

    ' * * With plain text: * * $('still-first').replace('Melon, oranges and grapes.'); - * // -> HTMLElement (p#still-first) + * // -> Element (p#still-first) * - * $('fruits').innerHTML + * $('fruits').innerHTML; * // -> 'Melon, oranges and grapes.' * * Finally, relying on the `toString()` method: * * $('fruits').replace(123); - * // -> HTMLElement + * // -> Element * * $('food').innerHTML; * // -> '123' @@ -757,7 +770,7 @@ Element.Methods = { * If the given element exists on the page, [[Element.wrap]] will wrap it in * place — its position will remain the same. * - * The `wrapper` argument can be _either_ an existing `HTMLElement` _or_ a + * The `wrapper` argument can be _either_ an existing [[Element]] _or_ a * string representing the tag name of an element to be created. The optional * `attributes` argument can contain a list of attribute/value pairs that * will be set on the wrapper using [[Element.writeAttribute]]. @@ -766,6 +779,7 @@ Element.Methods = { * * Original HTML: * + * language: html * * * @@ -790,6 +804,7 @@ Element.Methods = { * * Resulting HTML: * + * language: html *
    *
    Foo
    * @@ -830,13 +845,16 @@ Element.Methods = { * * For more information on `inspect` methods, see [[Object.inspect]]. * + * language: html *
      *
    • Golden Delicious
    • *
    • Mutsu
    • *
    • McIntosh
    • *
    • *
    - * + * + * And the associated JavaScript: + * * $('golden-delicious').inspect(); * // -> '
  • ' * @@ -880,6 +898,7 @@ Element.Methods = { * * ##### Examples * + * language: html *
      *
    • *
        @@ -890,7 +909,9 @@ Element.Methods = { *
      *
    • *
    - * + * + * And the associated JavaScript: + * * $('fruits').recursivelyCollect('firstChild'); * // -> [li#apples, ul#list-of-apples, li#golden-delicious, p] **/ @@ -975,6 +996,8 @@ Element.Methods = { * * * + * Then: + * * $('australopithecus').firstDescendant(); * // -> div#homo-herectus * @@ -1035,7 +1058,9 @@ Element.Methods = { *
  • McIntosh
  • *
  • Ida Red
  • * - * + * + * Then: + * * $('mcintosh').previousSiblings(); * // -> [li#mutsu, li#golden-delicious] * @@ -1072,7 +1097,9 @@ Element.Methods = { *
  • McIntosh
  • *
  • Ida Red
  • * - * + * + * Then: + * * $('mutsu').nextSiblings(); * // -> [li#mcintosh, li#ida-red] * @@ -1108,6 +1135,8 @@ Element.Methods = { *
  • Ida Red
  • * * + * Then: + * * $('mutsu').siblings(); * // -> [li#golden-delicious, li#mcintosh, li#ida-red] **/ @@ -1136,7 +1165,9 @@ Element.Methods = { * * * - * + * + * Then: + * * $('fruits').match('ul'); * // -> true * @@ -1616,7 +1647,9 @@ Element.Methods = { *

    An apple a day keeps the doctor away.

    * * - * + * + * Then: + * * $('apples').select('[title="yummy!"]'); * // -> [h3, li#golden-delicious, li#mutsu] * @@ -1738,8 +1771,11 @@ Element.Methods = { * * ##### Examples * + * language: html * - * + * + * Then: + * * $('tag').readAttribute('href'); * // -> '/tags/prototype' * @@ -1806,8 +1842,11 @@ Element.Methods = { * * ##### Examples * + * language: html *
    - * + * + * Then: + * * $('rectangle').getHeight(); * // -> 100 **/ @@ -1831,8 +1870,11 @@ Element.Methods = { * * ##### Examples * + * language: html *
    - * + * + * Then: + * * $('rectangle').getWidth(); * // -> 200 **/ @@ -1861,8 +1903,11 @@ Element.Methods = { * * ##### Examples * + * language: html *
    - * + * + * Then: + * * $('mutsu').hasClassName('fruit'); * // -> true * @@ -1938,8 +1983,11 @@ Element.Methods = { * * ##### Examples * + * language: html *
    - * + * + * Then: + * * $('mutsu').hasClassName('fruit'); * // -> false * @@ -2128,8 +2176,9 @@ Element.Methods = { * } * *
    - * - * + * + * Then: + * * $('test').getStyle('margin-left'); * // -> '1em' in Internet Explorer, * // -> '12px' elsewhere. @@ -2265,8 +2314,11 @@ Element.Methods = { * * ##### Examples * + * language: html *
    - * + * + * Then: + * * var dimensions = $('rectangle').getDimensions(); * // -> {width: 200, height: 100} * @@ -2366,9 +2418,14 @@ Element.Methods = { * example * * + * Then: + * * $('framer').makeClipping().setStyle({width: '100px', height: '100px'}); * // -> Element * + * Another example: + * + * language: html * Click me to try it out. * *
    @@ -2411,15 +2468,21 @@ Element.Methods = { * * ##### Example * + * language: html *
    * example *
    - * + * + * Then: + * * $('framer').undoClipping(); - * // -> HTMLElement (and sets the CSS overflow property to its original value). + * // -> Element (and sets the CSS overflow property to its original value). * + * Another example: + * + * language: html * Click me to try it out. - * + * *
    * example *
    @@ -3234,8 +3297,11 @@ Element.Methods.Simulated = { * * ##### Example * + * language: html * Prototype - * + * + * Then: + * * $('link').hasAttribute('href'); * // -> true **/ @@ -3614,7 +3680,7 @@ document.viewport = { * ##### Example * * document.viewport.getDimensions(); - * //-> { width: 776, height: 580 } + * //-> { width: 776, height: 580 } **/ getDimensions: function() { return { width: this.getWidth(), height: this.getHeight() }; @@ -3631,11 +3697,11 @@ document.viewport = { * ##### Examples * * document.viewport.getScrollOffsets(); - * //-> { left: 0, top: 0 } + * //-> { left: 0, top: 0 } * - * window.scrollTo(0, 120); - * document.viewport.getScrollOffsets(); - * //-> { left: 0, top: 120 } + * window.scrollTo(0, 120); + * document.viewport.getScrollOffsets(); + * //-> { left: 0, top: 120 } **/ getScrollOffsets: function() { return Element._returnOffset( diff --git a/src/dom/event.js b/src/dom/event.js index ee4a0dde0..9a3e84460 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -593,6 +593,7 @@ * individual elements within the container. This is sometimes called "event * delegation". It's particularly handy for tables: * + * language: html *
    * * diff --git a/src/dom/form.js b/src/dom/form.js index 1e71f23ec..861d3ce9a 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -341,6 +341,7 @@ Form.Methods = { * * Suppose you have this HTML form: * + * language: html * *
    User info *
      @@ -598,6 +599,7 @@ Form.Element.Methods = { * On the following form's submit event, the presence of each text input is * checked and lets the user know if they left a text input blank. * + * language: html * *
      * User Details @@ -898,6 +900,7 @@ Form.Element.Observer = Class.create(Abstract.TimedObserver, { * if any of the values had been changed. It returns to its initial state when * the data is submitted (saved). * + * language: html * *
      * Login Preferences From db8e8a0c213ea0e5f8e15fa78fb7d6c1b86cc240 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 12 Mar 2010 21:32:18 +0100 Subject: [PATCH 190/502] Make the Array#concat replacement for the broken native Array#concat implementation in Opera ES5 compliant. This fixes the issues described in #972 and #843. Also added more tests for Array#concat. --- src/lang/array.js | 18 +++++++++++------- test/unit/array_test.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/lang/array.js b/src/lang/array.js index f9ff92f47..fac72a838 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -446,17 +446,21 @@ Array.from = $A; } // Replaces a built-in function. No PDoc needed. - function concat() { - var array = slice.call(this, 0), item; - for (var i = 0, length = arguments.length; i < length; i++) { - item = arguments[i]; + function concat(_) { + var array = [], items = slice.call(arguments, 0), item, n = 0; + items.unshift(this); + for (var i = 0, length = items.length; i < length; i++) { + item = items[i]; if (Object.isArray(item) && !('callee' in item)) { - for (var j = 0, arrayLength = item.length; j < arrayLength; j++) - array.push(item[j]); + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) { + if (j in item) array[n] = item[j]; + n++; + } } else { - array.push(item); + array[n++] = item; } } + array.length = n; return array; } diff --git a/test/unit/array_test.js b/test/unit/array_test.js index 516d52a0a..bfa3dbb0e 100644 --- a/test/unit/array_test.js +++ b/test/unit/array_test.js @@ -182,7 +182,35 @@ new Test.Unit.Runner({ this.assertEnumEqual(['a', 'b', 'c', 'd'], $w(' a b\nc\t\nd\n')); }, - testConcat: function(){ + testConcat: function() { + var x = {}; + + this.assertIdentical(1, Array.prototype.concat.length); + + this.assertEnumEqual([0, 1], [0, 1].concat()); + this.assertIdentical(2, [0, 1].concat().length); + + this.assertEnumEqual([0, 1, 2, 3, 4], [].concat([0, 1], [2, 3, 4])); + this.assertIdentical(5, [].concat([0, 1], [2, 3, 4]).length); + + this.assertEnumEqual([0, x, 1, 2, true, "NaN"], [0].concat(x, [1, 2], true, "NaN")); + this.assertIdentical(6, [0].concat(x, [1, 2], true, "NaN").length); + + this.assertEnumEqual([undefined, 1, undefined], [,1].concat([], [,])); + this.assertIdentical(3, [,1].concat([], [,]).length); + this.assertEnumEqual([1], Object.keys([,1].concat([], [,]))); + + // Check that Array.prototype.concat can be used in a generic way + x.concat = Array.prototype.concat; + this.assertEnumEqual([x], x.concat()); + this.assertIdentical(1, x.concat().length); + + // Checking an edge case + var arr = []; arr[2] = true; + this.assertEnumEqual([undefined, undefined, true], [].concat(arr)); + this.assertIdentical(3, [].concat(arr).length); + this.assertEnumEqual([2], Object.keys([].concat(arr))); + var args = (function() { return [].concat(arguments) })(1, 2); this.assertIdentical(1, args[0][0]); } From c3088e3f623c4b9ac09fa20475a0a657cb1290b7 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sat, 13 Mar 2010 00:28:16 +0100 Subject: [PATCH 191/502] Make Array#map, Array#some, Array#every and Array#filter implementations (almost) ES5 compliant and use native versions when possible, yielding huge performance improvements. Prototype's implementations of these methods and the ES5 spec differ in the way a missing iterator function is handled: Prototype uses Prototype.K as a default iterator, while ES5 specifies that a TypeError has to be thrown. Implementing the ES5 spec completely would break backward compatibility of Prototype and would force users to pass Prototype.K manually. To keep backward compatibility when using browser native methods for Array#every and so on, these methods get wrapped. Some early performance benchmarks show that this wrapping has almost no performance impact. This change should also fix #765, as the fallback implementations for the ES5 methods all can be called in a generic way. --- src/lang/array.js | 95 ++++++++++++++++++++++++++++++++++++++++ test/unit/array_test.js | 96 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/src/lang/array.js b/src/lang/array.js index fac72a838..25a35d778 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -463,6 +463,91 @@ Array.from = $A; array.length = n; return array; } + + function wrapNative(method) { + return function() { + if (arguments.length === 0) { + return method.call(this, Prototype.K); + } else if (arguments[0] === undefined) { + var args = slice.call(arguments, 1); + args.unshift(Prototype.K) + return method.apply(this, args); + } else { + return method.apply(this, arguments); + } + }; + } + + if (arrayProto.map) { + var map = wrapNative(Array.prototype.map); + } else { + function map(iterator) { + iterator = iterator || Prototype.K; + var results = [], context = arguments[1], n = 0; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this) { + results[n] = iterator.call(context, this[i], i, this); + } + n++; + } + results.length = n; + return results; + } + } + + if (arrayProto.filter) { + var filter = wrapNative(Array.prototype.filter); + } else { + function filter(iterator) { + iterator = iterator || Prototype.K; + var results = [], context = arguments[1], value; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this) { + value = this[i]; + if (iterator.call(context, value, i, this)) { + results.push(value); + } + } + } + return results; + } + } + + if (arrayProto.some) { + var some = wrapNative(Array.prototype.some); + } else { + function some(iterator) { + iterator = iterator || Prototype.K; + var context = arguments[1]; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this && iterator.call(context, this[i], i, this)) { + return true; + } + } + + return false; + } + } + + if (arrayProto.every) { + var every = wrapNative(Array.prototype.every); + } else { + function every(iterator) { + iterator = iterator || Prototype.K; + var context = arguments[1]; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this && !iterator.call(context, this[i], i, this)) { + return false; + } + } + + return true; + } + } Object.extend(arrayProto, Enumerable); @@ -471,6 +556,16 @@ Array.from = $A; Object.extend(arrayProto, { _each: _each, + + map: map, + collect: map, + filter: filter, + findAll: filter, + some: some, + any: some, + every: every, + all: every, + clear: clear, first: first, last: last, diff --git a/test/unit/array_test.js b/test/unit/array_test.js index bfa3dbb0e..ca8de1b5f 100644 --- a/test/unit/array_test.js +++ b/test/unit/array_test.js @@ -213,5 +213,99 @@ new Test.Unit.Runner({ var args = (function() { return [].concat(arguments) })(1, 2); this.assertIdentical(1, args[0][0]); - } + }, + + testMapGeneric: function() { + var result = Array.prototype.map.call({0:0, 1:1, length:2}); + this.assertEnumEqual([0, 1], result); + }, + + testMap: function() { + this.assertEnumEqual([1,2,3], [1,2,3].map()); + this.assertEnumEqual([2,4,6], [1,2,3].map(function(x) { return x * 2; })); + + var x = [1,2,3,4]; + delete x[1]; + delete x[3]; + this.assertEnumEqual([1, undefined, 3, undefined], x.map()); + this.assertIdentical(4, x.map().length); + + var traversed = []; + x.map(function(val) { + traversed.push(val); + }); + this.assertEnumEqual([1, 3], traversed); + this.assertIdentical(2, traversed.length); + }, + + testFindAllGeneric: function() { + var result = Array.prototype.findAll.call({0:0, 1:1, length:2}, function(x) { + return x === 1; + }); + this.assertEnumEqual([1], result); + }, + + testFindAll: function() { + this.assertEnumEqual([2, 4, 6], [1, 2, 3, 4, 5, 6].findAll(function(x) { + return (x % 2) == 0; + })); + + var x = [1,2,3], traversed = []; + delete x[1]; + x.findAll(function(val) { traversed.push(val); }); + this.assertEnumEqual([1, 3], traversed); + this.assertIdentical(2, traversed.length); + }, + + testAnyGeneric: function() { + this.assert(Array.prototype.any.call({ 0:false, 1:true, length:2 })); + this.assert(!Array.prototype.any.call({ 0:false, 1:false, length:2 })); + }, + + testAny: function() { + this.assert(!([].any())); + + this.assert([true, true, true].any()); + this.assert([true, false, false].any()); + this.assert(![false, false, false].any()); + + this.assert([1,2,3,4,5].any(function(value) { + return value > 2; + })); + this.assert(![1,2,3,4,5].any(function(value) { + return value > 5; + })); + + var x = [1,2,3], traversed = []; + delete x[1]; + x.any(function(val) { traversed.push(val); }); + this.assertEnumEqual([1, 3], traversed); + this.assertIdentical(2, traversed.length); + }, + + testAllGeneric: function() { + this.assert(Array.prototype.all.call({ 0:true, 1:true, length:2 })); + this.assert(!Array.prototype.all.call({ 0:false, 1:true, length:2 })); + }, + + testAll: function() { + this.assert([].all()); + + this.assert([true, true, true].all()); + this.assert(![true, false, false].all()); + this.assert(![false, false, false].all()); + + this.assert([1,2,3,4,5].all(function(value) { + return value > 0; + })); + this.assert(![1,2,3,4,5].all(function(value) { + return value > 1; + })); + + var x = [1,2,3], traversed = []; + delete x[1]; + x.all(function(val) { traversed.push(val); return true; }); + this.assertEnumEqual([1, 3], traversed); + this.assertIdentical(2, traversed.length); + }, }); \ No newline at end of file From ab6f558b63ebd2c302576ddb7e77323ddc7feecf Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sat, 13 Mar 2010 01:02:16 +0100 Subject: [PATCH 192/502] Don't wrap the native Array.prototype.filter, as it requires an iterator anyway. Make Array#filter raise an TypeError when no iterator is given (ES5 compliant). --- src/lang/array.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/array.js b/src/lang/array.js index 25a35d778..9699bd262 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -497,10 +497,10 @@ Array.from = $A; } if (arrayProto.filter) { - var filter = wrapNative(Array.prototype.filter); + var filter = Array.prototype.filter; } else { function filter(iterator) { - iterator = iterator || Prototype.K; + if (!Object.isFunction(iterator)) { throw new TypeError(); } var results = [], context = arguments[1], value; for (var i = 0, length = this.length; i < length; i++) { From 7c4c79793ad1b61d6e5d0234997bba5b2af96b2d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sat, 13 Mar 2010 10:30:49 +0100 Subject: [PATCH 193/502] Also use native Array#filter for Array#select. --- src/lang/array.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lang/array.js b/src/lang/array.js index 9699bd262..ea8a1bdf0 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -559,6 +559,7 @@ Array.from = $A; map: map, collect: map, + select: filter, filter: filter, findAll: filter, some: some, From 1a4fb20237177f8fdfe773324fdbdb32b35141fa Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 14 Mar 2010 18:06:23 +0100 Subject: [PATCH 194/502] Add a missing semicolon. --- src/lang/array.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/array.js b/src/lang/array.js index ea8a1bdf0..eda1adf16 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -585,7 +585,7 @@ Array.from = $A; // fix for opera var CONCAT_ARGUMENTS_BUGGY = (function() { return [].concat(arguments)[0][0] !== 1; - })(1,2) + })(1,2); if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; From 84c7f29e36f8149da10b11fe934e4a01a9371981 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 14 Mar 2010 18:49:57 +0100 Subject: [PATCH 195/502] Make all Enumerable methods also pass the collection to the iterator function. This makes Enumerable methods work like all the iteration methods described in the ES5 spec. --- src/lang/enumerable.js | 51 ++++++------ test/unit/enumerable_test.js | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 25 deletions(-) diff --git a/src/lang/enumerable.js b/src/lang/enumerable.js index 4d17e6219..f1423e46d 100644 --- a/src/lang/enumerable.js +++ b/src/lang/enumerable.js @@ -109,8 +109,9 @@ var Enumerable = (function() { function each(iterator, context) { var index = 0; try { + var collection = this; this._each(function(value) { - iterator.call(context, value, index++); + iterator.call(context, value, index++, collection); }); } catch (e) { if (e != $break) throw e; @@ -186,9 +187,9 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); + result = result && !!iterator.call(context, value, index, this); if (!result) throw $break; - }); + }, this); return result; } @@ -218,9 +219,9 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) + if (result = !!iterator.call(context, value, index, this)) throw $break; - }); + }, this); return result; } @@ -251,8 +252,8 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); + results.push(iterator.call(context, value, index, this)); + }, this); return results; } @@ -274,11 +275,11 @@ var Enumerable = (function() { function detect(iterator, context) { var result; this.each(function(value, index) { - if (iterator.call(context, value, index)) { + if (iterator.call(context, value, index, this)) { result = value; throw $break; } - }); + }, this); return result; } @@ -299,9 +300,9 @@ var Enumerable = (function() { function findAll(iterator, context) { var results = []; this.each(function(value, index) { - if (iterator.call(context, value, index)) + if (iterator.call(context, value, index, this)) results.push(value); - }); + }, this); return results; } @@ -345,8 +346,8 @@ var Enumerable = (function() { this.each(function(value, index) { if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); + results.push(iterator.call(context, value, index, this)); + }, this); return results; } @@ -449,8 +450,8 @@ var Enumerable = (function() { **/ function inject(memo, iterator, context) { this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); + memo = iterator.call(context, memo, value, index, this); + }, this); return memo; } @@ -514,10 +515,10 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator.call(context, value, index); + value = iterator.call(context, value, index, this); if (result == null || value >= result) result = value; - }); + }, this); return result; } @@ -554,10 +555,10 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator.call(context, value, index); + value = iterator.call(context, value, index, this); if (result == null || value < result) result = value; - }); + }, this); return result; } @@ -592,9 +593,9 @@ var Enumerable = (function() { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator.call(context, value, index) ? + (iterator.call(context, value, index, this) ? trues : falses).push(value); - }); + }, this); return [trues, falses]; } @@ -636,9 +637,9 @@ var Enumerable = (function() { function reject(iterator, context) { var results = []; this.each(function(value, index) { - if (!iterator.call(context, value, index)) + if (!iterator.call(context, value, index, this)) results.push(value); - }); + }, this); return results; } @@ -668,9 +669,9 @@ var Enumerable = (function() { return this.map(function(value, index) { return { value: value, - criteria: iterator.call(context, value, index) + criteria: iterator.call(context, value, index, this) }; - }).sort(function(left, right) { + }, this).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); diff --git a/test/unit/enumerable_test.js b/test/unit/enumerable_test.js index 3e0d00bd8..27bd024c5 100644 --- a/test/unit/enumerable_test.js +++ b/test/unit/enumerable_test.js @@ -24,6 +24,16 @@ new Test.Unit.Runner({ this.assertEqual('1, 3', results.join(', ')); }, + "test #each passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.each(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + }, this); + }, + testEachChaining: function() { this.assertEqual(Fixtures.Primes, Fixtures.Primes.each(Prototype.emptyFunction)); this.assertEqual(3, Fixtures.Basic.each(Prototype.emptyFunction).length); @@ -63,6 +73,19 @@ new Test.Unit.Runner({ })); }, + "test #any passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.any(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + + // Iterate over all values + return value > 5; + }, this); + }, + testAll: function() { this.assert([].all()); @@ -78,6 +101,19 @@ new Test.Unit.Runner({ })); }, + "test #all passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.all(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + + // Iterate over all values + return value > 0; + }, this); + }, + testCollect: function() { this.assertEqual(Fixtures.Nicknames.join(', '), Fixtures.People.collect(function(person) { @@ -87,6 +123,17 @@ new Test.Unit.Runner({ this.assertEqual(26, Fixtures.Primes.map().length); }, + "test #collect passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.collect(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value; + }, this); + }, + testDetect: function() { this.assertEqual('Marcel Molina Jr.', Fixtures.People.detect(function(person) { @@ -94,6 +141,19 @@ new Test.Unit.Runner({ }).name); }, + "test #detect passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.detect(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + + // Iterate over all values + return value > 5; + }, this); + }, + testEachSlice: function() { this.assertEnumEqual([], [].eachSlice(2)); this.assertEqual(1, [1].eachSlice(1).length); @@ -125,6 +185,17 @@ new Test.Unit.Runner({ Fixtures.Z.findAll(prime).join(', ')); }, + "test #findAll passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.findAll(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value > 5; + }, this); + }, + testGrep: function() { this.assertEqual('noradio, htonl', Fixtures.Nicknames.grep(/o/).join(", ")); @@ -146,6 +217,17 @@ new Test.Unit.Runner({ this.assertEnumEqual(['|a', 'c|'], ['|a','b','c|'].grep('|')); }, + "test #grep passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.grep(/\d/, function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value; + }, this); + }, + testInclude: function() { this.assert(Fixtures.Nicknames.include('sam-')); this.assert(Fixtures.Nicknames.include('noradio')); @@ -191,6 +273,18 @@ new Test.Unit.Runner({ })); }, + "test #inject passes memo, value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.inject(0, function(memo, value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + + return memo + value; + }, this); + }, + testInvoke: function() { var result = [[2, 1, 3], [6, 5, 4]].invoke('sort'); this.assertEqual(2, result.length); @@ -209,12 +303,34 @@ new Test.Unit.Runner({ this.assertEqual('sam-', Fixtures.Nicknames.max()); // ?s > ?U }, + "test #max passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.max(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value; + }, this); + }, + testMin: function() { this.assertEqual(1, Fixtures.Z.min()); this.assertEqual(0, [ 1, 2, 3, 4, 5, 6, 7, 8, 0, 9 ].min()); this.assertEqual('Ulysses', Fixtures.Nicknames.min()); // ?U < ?h }, + "test #min passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.min(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value; + }, this); + }, + testPartition: function() { var result = Fixtures.People.partition(function(person) { return person.name.length < 15; @@ -225,6 +341,17 @@ new Test.Unit.Runner({ this.assertEqual('noradio, Ulysses', result[1].join(', ')); }, + "test #partition passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.partition(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value < 2; + }, this); + }, + testPluck: function() { this.assertEqual(Fixtures.Nicknames.join(', '), Fixtures.People.pluck('nickname').join(', ')); @@ -240,6 +367,17 @@ new Test.Unit.Runner({ }).join(', ')); }, + "test #reject passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.reject(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value < 2; + }, this); + }, + testSortBy: function() { this.assertEqual('htonl, noradio, sam-, Ulysses', Fixtures.People.sortBy(function(value) { @@ -247,6 +385,17 @@ new Test.Unit.Runner({ }).pluck('nickname').join(', ')); }, + "test #sortBy passes value, index and collection to the iterator": function() { + var i = 0; + Fixtures.Basic.sortBy(function(value, index, collection) { + this.assertIdentical(Fixtures.Basic[i], value); + this.assertIdentical(i, index); + this.assertIdentical(Fixtures.Basic, collection); + i++; + return value; + }, this); + }, + testToArray: function() { var result = Fixtures.People.toArray(); this.assert(result != Fixtures.People); // they're different objects... From 110d84fd6b9783ece654868791bf7d47ad61ccb8 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 14 Mar 2010 19:02:54 +0100 Subject: [PATCH 196/502] Make all #_each Methods match behavior of Array#forEach according to the ES5 spec (see #554). This change breaks backwards compatibility, as User defined classes will have to update their #_each implementations, but improves performance of all enumeration methods and thus is propably worth it. --- src/ajax/responders.js | 4 ++-- src/lang/array.js | 4 ++-- src/lang/enumerable.js | 6 +----- src/lang/hash.js | 4 ++-- src/lang/range.js | 4 ++-- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/ajax/responders.js b/src/ajax/responders.js index daa93263a..0f5611e37 100644 --- a/src/ajax/responders.js +++ b/src/ajax/responders.js @@ -69,8 +69,8 @@ Ajax.Responders = { responders: [], - _each: function(iterator) { - this.responders._each(iterator); + _each: function(iterator, context) { + this.responders._each(iterator, context); }, /** diff --git a/src/lang/array.js b/src/lang/array.js index eda1adf16..859503221 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -189,9 +189,9 @@ Array.from = $A; slice = arrayProto.slice, _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available - function each(iterator) { + function each(iterator, context) { for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); + iterator.call(context, this[i], i, this); } if (!_each) _each = each; diff --git a/src/lang/enumerable.js b/src/lang/enumerable.js index f1423e46d..e6515e106 100644 --- a/src/lang/enumerable.js +++ b/src/lang/enumerable.js @@ -107,12 +107,8 @@ var Enumerable = (function() { * has a method to do that for you. **/ function each(iterator, context) { - var index = 0; try { - var collection = this; - this._each(function(value) { - iterator.call(context, value, index++, collection); - }); + this._each(iterator, context); } catch (e) { if (e != $break) throw e; } diff --git a/src/lang/hash.js b/src/lang/hash.js index f44d9404f..8fb261d76 100644 --- a/src/lang/hash.js +++ b/src/lang/hash.js @@ -93,12 +93,12 @@ var Hash = Class.create(Enumerable, (function() { **/ // Our _internal_ each - function _each(iterator) { + function _each(iterator, context) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; - iterator(pair); + iterator.call(context, pair); } } diff --git a/src/lang/range.js b/src/lang/range.js index 208637df6..175a4ea21 100644 --- a/src/lang/range.js +++ b/src/lang/range.js @@ -105,10 +105,10 @@ var ObjectRange = Class.create(Enumerable, (function() { this.exclusive = exclusive; } - function _each(iterator) { + function _each(iterator, context) { var value = this.start; while (this.include(value)) { - iterator(value); + iterator.call(context, value); value = value.succ(); } } From c71417976a866d20426ff3435f38b07162b0e699 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 14 Mar 2010 19:39:36 +0100 Subject: [PATCH 197/502] Also update Element.ClassNames#_each (even tho it's deprecated and will be removed some time). --- src/deprecated.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/deprecated.js b/src/deprecated.js index 4f4867790..26351b7a5 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -156,10 +156,10 @@ Element.ClassNames.prototype = { this.element = $(element); }, - _each: function(iterator) { + _each: function(iterator, context) { this.element.className.split(/\s+/).select(function(name) { return name.length > 0; - })._each(iterator); + })._each(iterator, context); }, set: function(className) { From 1b8538cb20317542d96de420e07a02e2b918d155 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 14 Mar 2010 20:18:33 +0100 Subject: [PATCH 198/502] Use native Array.prototype.reduce for Array#inject, if it's available. --- src/lang/array.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lang/array.js b/src/lang/array.js index 859503221..c1eed8202 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -548,6 +548,20 @@ Array.from = $A; return true; } } + + if (arrayProto.reduce) { + // Keep a copy of the native reduce + var _reduce = arrayProto.reduce; + function inject(memo, iterator) { + iterator = iterator || Prototype.K; + var context = arguments[2]; + // The iterator has to be bound, as Array.prototype.reduce + // always executes the iterator in the global context. + return _reduce.call(this, iterator.bind(context), memo, context); + } + } else { + var inject = Enumerable.inject; + } Object.extend(arrayProto, Enumerable); @@ -566,6 +580,7 @@ Array.from = $A; any: some, every: every, all: every, + inject: inject, clear: clear, first: first, From 91e5582652e428364a24df8c90c9d1db3ae22703 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 19 Mar 2010 18:57:44 -0500 Subject: [PATCH 199/502] Make `Element.Layout` properly interpret computed non-integer pixel values. (e.g., Firefox can report "12.5px" as a computed style value.) (henrymazza) --- CHANGELOG | 2 ++ src/dom/layout.js | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9ce9c54ec..1d506237c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Make `Element.Layout` properly interpret computed non-integer pixel values. (e.g., Firefox can report "12.5px" as a computed style value.) (henrymazza) + * Fix deprecated Selector.matchElements. (Tobie Langel) * Make Object.keys ES5 compliant. (Tobie Langel) diff --git a/src/dom/layout.js b/src/dom/layout.js index 6763ec36b..92904d56a 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -23,8 +23,10 @@ } // Non-IE browsers will always return pixels if possible. - if ((/^\d+(px)?$/i).test(value)) { - return window.parseInt(value, 10); + // (We use parseFloat instead of parseInt because Firefox can return + // non-integer pixel values.) + if ((/^\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); } // When IE gives us something other than a pixel value, this technique From f64a331d204c00b8e1b48cc779968c403f49dc2d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 24 Mar 2010 19:10:50 -0500 Subject: [PATCH 200/502] Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) --- src/dom/event.js | 96 +++++++++++++++++++++++++++++++++++++- test/functional/event.html | 91 ++++++++++++++++++++++++++++++++---- 2 files changed, 176 insertions(+), 11 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 9a3e84460..42edd8f23 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -852,14 +852,98 @@ return Event.extend(event); } + + /** + * class Event.Handler + * + * Creates an observer on an element that listens for a particular event on + * that element's descendants, optionally filtering by a CSS selector. + * + * This class simplifies the common "event delegation" pattern, in which one + * avoids adding an observer to a number of individual elements and instead + * listens on a _common ancestor_ element. + * + **/ + Event.Handler = Class.create({ + /** + * new Event.Handler(element, eventName[, selector], callback) + * - element (Element): The element to listen on. + * - eventName (String): An event to listen for. Can be a standard browser + * event or a custom event. + * - selector (String): A CSS selector. If specified, will call `callback` + * _only_ when it can find an element that matches `selector` somewhere + * in the ancestor chain between the event's target element and the + * given `element`. + * - callback (Function): The event handler function. Should expect two + * arguments: the event object _and_ the element that received the + * event. (If `selector` was given, this element will be the one that + * satisfies the criteria described just above; if not, it will be the + * one specified in the `element` argument). + * + * Instantiates an `Event.Handler`. **Will not** begin observing until + * [[Event.Handler#start]] is called. + **/ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + /** + * Event.Handler#start -> Event.Handler + * + * Starts listening for events. Returns itself. + **/ + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + /** + * Event.Handler#stop -> Event.Handler + * + * Stops listening for events. Returns itself. + **/ + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, + + handleEvent: function(event) { + var element = this.selector ? event.findElement(this.selector) : + this.element; + if (element) this.callback.call(element, event, element); + } + }); + + /** + * Event.on(element, eventName, selector, callback) -> Event.Handler + * + * Listens for events on an element's descendants, optionally filtering + * to match a given CSS selector. + * + * Creates an instance of [[Event.Handler]], calls [[Event.Handler#start]], + * then returns that instance. Keep a reference to this returned instance if + * you later want to unregister the observer. + **/ + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } + + return new Event.Handler(element, eventName, selector, callback).start(); + } Object.extend(Event, Event.Methods); Object.extend(Event, { fire: fire, observe: observe, - stopObserving: stopObserving + stopObserving: stopObserving, + on: on }); Element.addMethods({ @@ -918,7 +1002,13 @@ * Element.stopObserving(@element[, eventName[, handler]]) -> Element * See [[Event.stopObserving]]. **/ - stopObserving: stopObserving + stopObserving: stopObserving, + + /** + * Element.on(@element, evnetName[, selector], callback) -> Element + * See [[Event.on]]. + **/ + on: on }); /** section: DOM @@ -982,6 +1072,8 @@ * [[Element.stopObserving]]. **/ stopObserving: stopObserving.methodize(), + + on: on.methodize(), /** * document.loaded -> Boolean diff --git a/test/functional/event.html b/test/functional/event.html index 77c2a3243..9435042e9 100644 --- a/test/functional/event.html +++ b/test/functional/event.html @@ -8,19 +8,32 @@ @@ -53,7 +66,10 @@

      Prototype functional tests for the Event module

      -
      log empty
      +
      + Log +
      +

      A basic event test - click here

      click to stop observing the first test

      @@ -63,13 +79,13 @@

      Prototype functional tests for the Event module

      + +
      + Event delegation +
        +
      • + Child 1 (click) +
      • +
      • + Child 2 (mouseover) +
      • +
      • + Child 3 (mouseup) +
      • +
      + + Results: + +
        +
      • Test 1
      • +
      • Test 2
      • +
      • Test 3
      • +
      +
      + + + From 82d44de631dc34215901f5dd0b2d967bb0fd6033 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 30 Mar 2010 17:34:24 -0500 Subject: [PATCH 201/502] Fix typo. Update CHANGELOG. --- CHANGELOG | 2 ++ src/dom/event.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1d506237c..e335b96ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) + * Make `Element.Layout` properly interpret computed non-integer pixel values. (e.g., Firefox can report "12.5px" as a computed style value.) (henrymazza) * Fix deprecated Selector.matchElements. (Tobie Langel) diff --git a/src/dom/event.js b/src/dom/event.js index 42edd8f23..6efac7da3 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -1005,7 +1005,7 @@ stopObserving: stopObserving, /** - * Element.on(@element, evnetName[, selector], callback) -> Element + * Element.on(@element, eventName[, selector], callback) -> Element * See [[Event.on]]. **/ on: on From 46b96b9571cfd5abcdd4f816b7eaeceb12b81d06 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 31 Mar 2010 03:16:22 -0500 Subject: [PATCH 202/502] Add some contextual messages for DOM unit tests. --- test/unit/dom_test.js | 47 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index deb20261e..2b406083a 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1143,8 +1143,8 @@ new Test.Unit.Runner({ }, testElementGetWidth: function() { - this.assertIdentical(200, $('dimensions-visible').getWidth()); - this.assertIdentical(200, $('dimensions-display-none').getWidth()); + this.assertIdentical(200, $('dimensions-visible').getWidth(), '#dimensions-visible'); + this.assertIdentical(200, $('dimensions-display-none').getWidth(), '#dimensions-display-none'); }, testElementGetDimensions: function() { @@ -1297,35 +1297,35 @@ new Test.Unit.Runner({ testPositionedOffset: function() { this.assertEnumEqual([10,10], - $('body_absolute').positionedOffset()); + $('body_absolute').positionedOffset(), '#body_absolute'); this.assertEnumEqual([10,10], - $('absolute_absolute').positionedOffset()); + $('absolute_absolute').positionedOffset(), '#absolute_absolute'); this.assertEnumEqual([10,10], - $('absolute_relative').positionedOffset()); + $('absolute_relative').positionedOffset(), '#absolute_relative'); this.assertEnumEqual([0,10], - $('absolute_relative_undefined').positionedOffset()); + $('absolute_relative_undefined').positionedOffset(), '#absolute_relative_undefined'); this.assertEnumEqual([10,10], - $('absolute_fixed_absolute').positionedOffset()); + $('absolute_fixed_absolute').positionedOffset(), '#absolute_fixed_absolute'); var afu = $('absolute_fixed_undefined'); this.assertEnumEqual([afu.offsetLeft, afu.offsetTop], - afu.positionedOffset()); + afu.positionedOffset(), '#absolute_fixed_undefined'); var element = new Element('div'), offset = element.positionedOffset(); - this.assertEnumEqual([0,0], offset); - this.assertIdentical(0, offset.top); - this.assertIdentical(0, offset.left); + this.assertEnumEqual([0,0], offset, 'new element'); + this.assertIdentical(0, offset.top, 'new element top'); + this.assertIdentical(0, offset.left, 'new element left'); }, testCumulativeOffset: function() { var element = new Element('div'), offset = element.cumulativeOffset(); - this.assertEnumEqual([0,0], offset); - this.assertIdentical(0, offset.top); - this.assertIdentical(0, offset.left); + this.assertEnumEqual([0,0], offset, 'new element'); + this.assertIdentical(0, offset.top, 'new element top'); + this.assertIdentical(0, offset.left, 'new element left'); var innerEl = new Element('div'), outerEl = new Element('div'); outerEl.appendChild(innerEl); - this.assertEnumEqual([0,0], innerEl.cumulativeOffset()); + this.assertEnumEqual([0,0], innerEl.cumulativeOffset(), 'new inner element'); }, testViewportOffset: function() { @@ -1344,12 +1344,17 @@ new Test.Unit.Runner({ }, testOffsetParent: function() { - this.assertEqual('body_absolute', $('absolute_absolute').getOffsetParent().id); - this.assertEqual('body_absolute', $('absolute_relative').getOffsetParent().id); - this.assertEqual('absolute_relative', $('inline').getOffsetParent().id); - this.assertEqual('absolute_relative', $('absolute_relative_undefined').getOffsetParent().id); - - this.assertEqual(document.body, new Element('div').getOffsetParent()); + this.assertEqual('body_absolute', $('absolute_absolute').getOffsetParent().id, + '#body_absolute should be parent of #absolute_absolute'); + this.assertEqual('body_absolute', $('absolute_relative').getOffsetParent().id, + '#body_absolute should be parent of #absolute_relative'); + this.assertEqual('absolute_relative', $('inline').getOffsetParent().id, + '#absolute_relative should be parent of #inline'); + this.assertEqual('absolute_relative', $('absolute_relative_undefined').getOffsetParent().id, + '#absolute_relative should be parent of #absolute_relative_undefined'); + + this.assertEqual(document.body, new Element('div').getOffsetParent(), + 'body should be parent of unattached element'); }, testAbsolutize: function() { From 8369133edc44035fea91c99a41d12884da1a95a1 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 31 Mar 2010 03:16:43 -0500 Subject: [PATCH 203/502] Fix a handful of test failures in IE. --- src/dom/layout.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 92904d56a..b93be3dc3 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -21,7 +21,7 @@ if (value === null) { return null; } - + // Non-IE browsers will always return pixels if possible. // (We use parseFloat instead of parseInt because Firefox can return // non-integer pixel values.) @@ -38,7 +38,7 @@ value = element.style.pixelLeft; element.style.left = style; element.runtimeStyle.left = rStyle; - + return value; } @@ -304,9 +304,9 @@ // (b) it has an explicitly-set width, instead of width: auto. // Either way, it means the element is the width it needs to be // in order to report an accurate height. - newWidth = window.parseInt(width, 10); + newWidth = getPixelValue(width); } else if (width && (position === 'absolute' || position === 'fixed')) { - newWidth = window.parseInt(width, 10); + newWidth = getPixelValue(width); } else { // If not, that means the element's width depends upon the width of // its parent. @@ -716,7 +716,7 @@ * // -> 100 **/ function getDimensions(element) { - var layout = $(element).getLayout(); + var layout = $(element).getLayout(); return { width: layout.get('width'), height: layout.get('height') @@ -730,7 +730,11 @@ * `body` element is returned. **/ function getOffsetParent(element) { - if (element.offsetParent) return $(element.offsetParent); + if (isDetached(element)) return $(document.body); + + // IE reports offset parent incorrectly for inline elements. + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); if (element === document.body) return $(element); while ((element = element.parentNode) && element !== document.body) { @@ -910,13 +914,20 @@ return element.nodeName.toUpperCase() === 'BODY'; } + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + // If the browser supports the nonstandard `getBoundingClientRect` // (currently only IE and Firefox), it becomes far easier to obtain // true offsets. if ('getBoundingClientRect' in document.documentElement) { Element.addMethods({ viewportOffset: function(element) { - element = $(element); + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + var rect = element.getBoundingClientRect(), docEl = document.documentElement; // The HTML element on IE < 8 has a 2px border by default, giving @@ -928,6 +939,8 @@ cumulativeOffset: function(element) { element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + var docOffset = $(document.documentElement).viewportOffset(), elementOffset = element.viewportOffset(); return elementOffset.relativeTo(docOffset); @@ -935,7 +948,8 @@ positionedOffset: function(element) { element = $(element); - var parent = element.getOffsetParent(); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); // When the BODY is the offsetParent, IE6 mistakenly reports the // parent as HTML. Use that as the litmus test to fix another From 1928e6e9c1ebc5870784fc53f24163b9da86dfba Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Apr 2010 18:02:55 -0500 Subject: [PATCH 204/502] Remove some commented-out code. --- src/dom/layout.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index b93be3dc3..d8d8863dc 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -240,11 +240,6 @@ // TODO: Investigate. set: function(property, value) { throw "Properties of Element.Layout are read-only."; - // if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) { - // throw "Cannot set a composite property."; - // } - // - // return this._set(property, toCSSPixels(value)); }, /** From 0befd5fc2c9b2ee2bc1603723e74b19a4edc1e1a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Apr 2010 18:05:24 -0500 Subject: [PATCH 205/502] Bump version number. --- CHANGELOG | 2 ++ src/constants.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e335b96ae..b09bc61e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +*1.7_rc1* (April 1, 2010) + * Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) * Make `Element.Layout` properly interpret computed non-integer pixel values. (e.g., Firefox can report "12.5px" as a computed style value.) (henrymazza) diff --git a/src/constants.yml b/src/constants.yml index 9be5d270a..9728c12cd 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.6.1 +PROTOTYPE_VERSION: 1.7_rc1 From ece6e0e9928cd93d2524d85ff02ee7f1784cc331 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Tue, 6 Apr 2010 00:26:13 +0200 Subject: [PATCH 206/502] Update NWMatcher repo. Modify NWMatcher wrapper accordingly. --- vendor/nwmatcher/repository | 2 +- vendor/nwmatcher/selector_engine.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/nwmatcher/repository b/vendor/nwmatcher/repository index c9f5d5d4f..489fdd036 160000 --- a/vendor/nwmatcher/repository +++ b/vendor/nwmatcher/repository @@ -1 +1 @@ -Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664 +Subproject commit 489fdd0368e2adf957745cf816377732ee3eb301 diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 2d55a148a..e840c273b 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -3,7 +3,7 @@ Prototype._original_property = window.NW; Prototype.Selector = (function(engine) { function select(selector, scope) { - return engine.select(selector, scope || document, null, Element.extend); + return engine.select(selector, scope || document, Element.extend); } return { From 2f9bde3ad5a2e3dd104c812b6c81f4077fe0aa1e Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Tue, 6 Apr 2010 14:50:18 -0700 Subject: [PATCH 207/502] prototype: Simplify the NWMatcher adapter and optimize it for browsers that don't need to extend DOM elements. [jddalton] --- vendor/nwmatcher/selector_engine.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index e840c273b..9b0fae991 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -2,8 +2,12 @@ Prototype._original_property = window.NW; //= require "repository/src/nwmatcher" Prototype.Selector = (function(engine) { - function select(selector, scope) { - return engine.select(selector, scope || document, Element.extend); + var select = engine.select; + + if (Element.extend !== Prototype.K) { + select = function select(selector, scope) { + return engine.select(selector, scope, Element.extend); + }; } return { From d11f3173e6e767518e6348c2218e8c96c62e9c1a Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 7 Apr 2010 05:36:43 +0200 Subject: [PATCH 208/502] Update PDoc. --- .gitignore | 3 ++- Rakefile | 32 +++++++++++++------------------- vendor/pdoc | 2 +- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 2460a37a8..9a1118d7b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ pkg test/unit/tmp/* doc -tmp \ No newline at end of file +tmp +*.pdoc.yaml \ No newline at end of file diff --git a/Rakefile b/Rakefile index a61f3fd90..31069c5e7 100755 --- a/Rakefile +++ b/Rakefile @@ -65,29 +65,19 @@ module PrototypeHelper end def self.build_doc_for(file) - mkdir_p TMP_DIR - temp_path = File.join(TMP_DIR, "prototype.temp.js") - sprocketize( - :path => 'src', - :source => file, - :destination => temp_path, - :selector_engine => ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE, - :strip_comments => false - ) - rm_rf DOC_DIR - - highlighter = syntax_highlighter - puts "Using syntax highlighter: #{highlighter}\n" + rm_rf(DOC_DIR) + mkdir_p(DOC_DIR) PDoc.run({ - :source_files => [temp_path], + :source_files => Dir[File.join('src', '**', '*.js')], :destination => DOC_DIR, :index_page => 'README.markdown', - :syntax_highlighter => highlighter, - :markdown_parser => :bluecloth + :syntax_highlighter => syntax_highlighter, + :markdown_parser => :bluecloth, + :repository_url => "http://github.com/sstephenson/prototype/tree/#{current_head}/", + :pretty_urls => true, + :bust_cache => false }) - - rm_rf temp_path end def self.syntax_highlighter @@ -103,7 +93,7 @@ module PrototypeHelper def self.require_highlighter(name, verbose=false) case name when :pygments - success = system("pygmentize -V") + success = system("pygmentize -V > /dev/null") if !success && verbose puts "\nYou asked to use Pygments, but I can't find the 'pygmentize' binary." puts "To install, visit:\n" @@ -194,6 +184,10 @@ module PrototypeHelper exit end end + + def self.current_head + `git show-ref --hash HEAD`.chomp + end end task :default => [:dist, :dist_helper, :package, :clean_package_source] diff --git a/vendor/pdoc b/vendor/pdoc index 472a55dd0..14c5c89e0 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 472a55dd0019acf034d4f72522915a5e9efd0a1a +Subproject commit 14c5c89e0147376563370e5925eeccc80990f202 From f890b3de68c56c1bf0c876dfcd783aab9fc9ea91 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 7 Apr 2010 06:43:31 +0200 Subject: [PATCH 209/502] Update PDoc some more. --- Rakefile | 4 ++-- vendor/pdoc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 31069c5e7..8d205bde2 100755 --- a/Rakefile +++ b/Rakefile @@ -74,7 +74,7 @@ module PrototypeHelper :index_page => 'README.markdown', :syntax_highlighter => syntax_highlighter, :markdown_parser => :bluecloth, - :repository_url => "http://github.com/sstephenson/prototype/tree/#{current_head}/", + :repository_url => "http://github.com/sstephenson/prototype/blob/#{current_head}/", :pretty_urls => true, :bust_cache => false }) @@ -186,7 +186,7 @@ module PrototypeHelper end def self.current_head - `git show-ref --hash HEAD`.chomp + `git show-ref --hash HEAD`.chomp[0..6] end end diff --git a/vendor/pdoc b/vendor/pdoc index 14c5c89e0..c85d6b873 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 14c5c89e0147376563370e5925eeccc80990f202 +Subproject commit c85d6b873f63010f9f2dae3f2c16e559d3e9d50d From 748e73f00bdc7359bfb0d3ecbf61f5624e7ad313 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 7 Apr 2010 14:12:03 +0200 Subject: [PATCH 210/502] Update PDoc yet again. --- Rakefile | 8 +++++++- vendor/pdoc | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 8d205bde2..d66430f91 100755 --- a/Rakefile +++ b/Rakefile @@ -76,7 +76,13 @@ module PrototypeHelper :markdown_parser => :bluecloth, :repository_url => "http://github.com/sstephenson/prototype/blob/#{current_head}/", :pretty_urls => true, - :bust_cache => false + :bust_cache => false, + :name => 'Prototype JavaScript Framework', + :short_name => 'Prototype', + :home_url => 'http://prototypejs.org', + :doc_url => 'http://prototypejs.org/doc', + :version => PrototypeHelper::VERSION, + :copyright_notice => 'This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.' }) end diff --git a/vendor/pdoc b/vendor/pdoc index c85d6b873..4547525f0 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit c85d6b873f63010f9f2dae3f2c16e559d3e9d50d +Subproject commit 4547525f0c43693c296ad907cd73ddc9890ccf9f From 9f5da9b6f7c7b4a6a50ef54d91172d5f85373cb2 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 8 Apr 2010 04:05:28 +0200 Subject: [PATCH 211/502] Ahem. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index 4547525f0..2e4c7558e 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 4547525f0c43693c296ad907cd73ddc9890ccf9f +Subproject commit 2e4c7558e89bb252f061d2b093a307e79082410e From b1f4f28f62e521954906f50089b5c7aa3bc5e077 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Fri, 9 Apr 2010 14:15:28 -0500 Subject: [PATCH 212/502] Ensure Event#findElement only calls Selector.match with element nodes --- src/dom/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/event.js b/src/dom/event.js index 6efac7da3..023025ad8 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -228,7 +228,7 @@ var element = Event.element(event); if (!expression) return element; while (element) { - if (Prototype.Selector.match(element, expression)) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { return Element.extend(element); } element = element.parentNode; From 544d2ce1de6ffacd4a498eb91ba3c9966506484e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 9 Apr 2010 16:51:35 -0500 Subject: [PATCH 213/502] Work around an issue with `Element.cumulativeOffset` that was affecting sortable behavior in script.aculo.us. (sam, Andrew Dupont) --- CHANGELOG | 2 ++ src/deprecated.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b09bc61e3..09dd6088d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Work around an issue with `Element.cumulativeOffset` that was affecting sortable behavior in script.aculo.us. (sam, Andrew Dupont) + *1.7_rc1* (April 1, 2010) * Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) diff --git a/src/deprecated.js b/src/deprecated.js index 4f4867790..81f4cb9f7 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -64,8 +64,8 @@ var Position = { withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = Element.cumulativeScrollOffset(element); - this.xcomp = x + offsetcache[0] - this.deltaX; - this.ycomp = y + offsetcache[1] - this.deltaY; + this.xcomp = x - this.deltaX; + this.ycomp = y - this.deltaY; this.offset = Element.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && From 0b108e27cdc93989d88ffff2bc34d99605cfcf43 Mon Sep 17 00:00:00 2001 From: Juriy Zaytsev Date: Sat, 10 Apr 2010 12:55:02 -0400 Subject: [PATCH 214/502] Change order of queried elements passed to `$` (in `testCommasFor$$` test), as both Sizzle and NWMatcher return elements in document order. This fixes 2 failing assertions. --- test/unit/selector_test.js | 4 ++-- vendor/nwmatcher/repository | 2 +- vendor/pdoc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index 633b82b32..2d91199a9 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -336,8 +336,8 @@ new Test.Unit.Runner({ }, testCommasFor$$: function() { - this.assertEnumEqual($('list', 'p', 'link_1', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,*[xml:lang="es-us"] , #troubleForm')); - this.assertEnumEqual($('list', 'p', 'link_1', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '*[xml:lang="es-us"] , #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,*[xml:lang="es-us"] , #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '*[xml:lang="es-us"] , #troubleForm')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]')); }, diff --git a/vendor/nwmatcher/repository b/vendor/nwmatcher/repository index 489fdd036..c9f5d5d4f 160000 --- a/vendor/nwmatcher/repository +++ b/vendor/nwmatcher/repository @@ -1 +1 @@ -Subproject commit 489fdd0368e2adf957745cf816377732ee3eb301 +Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664 diff --git a/vendor/pdoc b/vendor/pdoc index 2e4c7558e..147250bd6 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 2e4c7558e89bb252f061d2b093a307e79082410e +Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b From 8456c981da5ccdde4ef22a20e2aa52d68d2d52bf Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Tue, 13 Apr 2010 11:28:44 -0500 Subject: [PATCH 215/502] Revert "Work around an issue with `Element.cumulativeOffset` that was affecting sortable behavior in script.aculo.us. (sam, Andrew Dupont)" This reverts commit 544d2ce1de6ffacd4a498eb91ba3c9966506484e. --- CHANGELOG | 2 -- src/deprecated.js | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 09dd6088d..b09bc61e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,3 @@ -* Work around an issue with `Element.cumulativeOffset` that was affecting sortable behavior in script.aculo.us. (sam, Andrew Dupont) - *1.7_rc1* (April 1, 2010) * Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) diff --git a/src/deprecated.js b/src/deprecated.js index 81f4cb9f7..4f4867790 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -64,8 +64,8 @@ var Position = { withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = Element.cumulativeScrollOffset(element); - this.xcomp = x - this.deltaX; - this.ycomp = y - this.deltaY; + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = Element.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && From 17b27ff6d20bbdca9d0a9d3ff7a4e00295775473 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Tue, 13 Apr 2010 11:36:27 -0500 Subject: [PATCH 216/502] Element#cumulativeOffset viewportOffset measurement is relative to document.body, not document.documentElement, when using getBoundingClientRect --- src/dom/layout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index d8d8863dc..ae96fd34f 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -936,7 +936,7 @@ element = $(element); if (isDetached(element)) return new Element.Offset(0, 0); - var docOffset = $(document.documentElement).viewportOffset(), + var docOffset = $(document.body).viewportOffset(), elementOffset = element.viewportOffset(); return elementOffset.relativeTo(docOffset); }, From bed63aa6e3a9c3d2a66143e8587ccad35aff6156 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 14 Apr 2010 00:20:49 +0200 Subject: [PATCH 217/502] Partially revert 0b108e27cdc93989d88ffff2bc34d99605cfcf43. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index 147250bd6..ecd28ea58 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 147250bd65eed627e32ca5a70b57fe4f7803ab4b +Subproject commit ecd28ea583998882af7712125522c60a7e41127c From c0e20dc3d2336b031683df4ceea6eaff670766c1 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 15 Apr 2010 02:42:40 +0200 Subject: [PATCH 218/502] doc: Move the Prototype namespace out of sections and make it top-level. --- src/prototype.js | 2 +- vendor/pdoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype.js b/src/prototype.js index 72928cf20..024599085 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -6,7 +6,7 @@ * *--------------------------------------------------------------------------*/ -/** section: Language +/** * Prototype * * The [[Prototype]] namespace provides fundamental information about the diff --git a/vendor/pdoc b/vendor/pdoc index ecd28ea58..d1a6ef090 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit ecd28ea583998882af7712125522c60a7e41127c +Subproject commit d1a6ef090bc3d644e427b64fae15ee28eeb0a6a9 From 2fd5c9bd029941f4b7c7d52445813ebe45f9a955 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 Apr 2010 13:31:25 -0400 Subject: [PATCH 219/502] Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] --- CHANGELOG | 2 ++ src/dom/layout.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b09bc61e3..de455088a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) + *1.7_rc1* (April 1, 2010) * Add an `Event.Handler` class, plus `Event.on` and `Element#on` methods, for simplified event delegation. (sam, Tobie Langel, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index ae96fd34f..d38dbb602 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -94,8 +94,8 @@ // Converts the layout hash property names back to the CSS equivalents. // For now, only the border properties differ. function cssNameFor(key) { - if (key.includes('border')) return key + '-width'; - return key; + if (key.includes('border')) key = key + '-width'; + return key.camelize(); } /** From 949dc83fd4533be0077eea5d6c92a78caa0aae98 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 Apr 2010 13:32:27 -0400 Subject: [PATCH 220/502] Add `Element.Layout#toObject` and `Element.Layout.toHash`. --- CHANGELOG | 2 ++ src/dom/layout.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index de455088a..8c94b1575 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) + Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) *1.7_rc1* (April 1, 2010) diff --git a/src/dom/layout.js b/src/dom/layout.js index d38dbb602..7a6881e58 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -338,6 +338,45 @@ return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, + /** + * Element.Layout#toObject([keys...]) -> Object + * - keys (String): A space-separated list of keys to include. + * + * Converts the layout hash to a plain object of key/value pairs, + * optionally including only the given keys. + * + * Keys can be passed into this method as individual arguments _or_ + * separated by spaces within a string. + **/ + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + // Key needs to be a valid Element.Layout property. + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }); + return obj; + }, + + /** + * Element.Layout#toHash([keys...]) -> Hash + * - keys (String): A space-separated list of keys to include. + * + * Converts the layout hash to an ordinary hash of key/value pairs, + * optionally including only the given keys. + * + * Keys can be passed into this method as individual arguments _or_ + * separated by spaces within a string. + **/ + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + /** * Element.Layout#toCSS([keys...]) -> Object * - keys (String): A space-separated list of keys to include. From 08a6188e0ebf55681feaacc094c57c2992662840 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 23 Apr 2010 02:04:16 -0500 Subject: [PATCH 221/502] Fix typo in layout.js. --- src/dom/layout.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 7a6881e58..e9178716c 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -885,11 +885,11 @@ } var offsetParent = getOffsetParent(element); - var eOffset = element.viewportOffset(), pOffset = - offsetParent.viewportOffset(); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); - var offset = eOffset.relativeTo(pOffset); - var layout = element.get('layout'); + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); element.store('prototype_absolutize_original_styles', { left: element.getStyle('left'), From eb263577c45eb2e6b839dc1260e5fd4fad996618 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 23 Apr 2010 02:04:58 -0500 Subject: [PATCH 222/502] Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. --- CHANGELOG | 2 ++ src/dom/layout.js | 15 ++++++++------- test/unit/layout_test.js | 23 +++++++++++++++++++++-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8c94b1575..4b4032aa2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) + Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index e9178716c..37e2dccc3 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -94,7 +94,7 @@ // Converts the layout hash property names back to the CSS equivalents. // For now, only the border properties differ. function cssNameFor(key) { - if (key.includes('border')) key = key + '-width'; + if (key.include('border')) key = key + '-width'; return key.camelize(); } @@ -358,7 +358,7 @@ if (!Element.Layout.PROPERTIES.include(key)) return; var value = this.get(key); if (value != null) obj[key] = value; - }); + }, this); return obj; }, @@ -398,6 +398,7 @@ var keys = (args.length === 0) ? Element.Layout.PROPERTIES : args.join(' ').split(' '); var css = {}; + keys.each( function(key) { // Key needs to be a valid Element.Layout property... if (!Element.Layout.PROPERTIES.include(key)) return; @@ -407,8 +408,8 @@ var value = this.get(key); // Unless the value is null, add 'px' to the end and add it to the // returned object. - if (value) css[cssNameFor(key)] = value + 'px'; - }); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); return css; }, @@ -443,12 +444,12 @@ var bTop = this.get('border-top'), bBottom = this.get('border-bottom'); - + var pTop = this.get('padding-top'), pBottom = this.get('padding-bottom'); - + if (!this._preComputing) this._end(); - + return bHeight - bTop - bBottom - pTop - pBottom; }, diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index bd1592131..278afee74 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -12,8 +12,6 @@ function isDisplayed(element) { } new Test.Unit.Runner({ - setup: function() { - }, 'test preCompute argument of layout': function() { var preComputedLayout = $('box1').getLayout(true), normalLayout = $('box1').getLayout(); @@ -95,5 +93,26 @@ new Test.Unit.Runner({ this.assertEqual(0, layout.get('top'), 'top'); this.assertIdentical($('box6_parent'), $('box6').getOffsetParent()); + }, + + 'test #toCSS, #toObject, #toHash': function() { + var layout = $('box6').getLayout(); + var top = layout.get('top'); + + var cssObject = layout.toCSS('top'); + + this.assert('top' in cssObject, + "layout object should have 'top' property"); + + cssObject = layout.toCSS('top left bottom'); + + $w('top left bottom').each( function(prop) { + this.assert(prop in cssObject, "layout object should have '" + + prop + "' property"); + }, this); + + var obj = layout.toObject('top'); + this.assert('top' in obj, + "object should have 'top' property"); } }); \ No newline at end of file From 443e1e1feca21b9b9e2d325e335fb814923b0255 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 2 May 2010 19:43:39 +0200 Subject: [PATCH 223/502] Add wrapper for mootools Slick CSS selector. Usage: $ git submodule init vendor/slick/repository $ git submodule update vendor/slick/repository $ rake dist SELECTOR_ENGINE=slick Run tests: $ rake test TESTS=selector SELECTOR_ENGINE=slick --- .gitmodules | 3 +++ vendor/slick/repository | 1 + vendor/slick/selector_engine.js | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 160000 vendor/slick/repository create mode 100644 vendor/slick/selector_engine.js diff --git a/.gitmodules b/.gitmodules index ee65a406d..e14ada56f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,6 @@ [submodule "vendor/sizzle/repository"] path = vendor/sizzle/repository url = git://github.com/jeresig/sizzle.git +[submodule "vendor/slick/repository"] + path = vendor/slick/repository + url = http://github.com/mootools/slick.git diff --git a/vendor/slick/repository b/vendor/slick/repository new file mode 160000 index 000000000..b7d3a3305 --- /dev/null +++ b/vendor/slick/repository @@ -0,0 +1 @@ +Subproject commit b7d3a3305675cfaa326c55b3eb1d4c4d4f962744 diff --git a/vendor/slick/selector_engine.js b/vendor/slick/selector_engine.js new file mode 100644 index 000000000..f49e02f1d --- /dev/null +++ b/vendor/slick/selector_engine.js @@ -0,0 +1,26 @@ +Prototype._original_property = window.Slick; +//= require "repository/Source/Slick.Parser.js" +//= require "repository/Source/Slick.Finder.js" + +Prototype.Selector = (function(engine) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + function select(selector, scope) { + return extend(engine.search(scope || document, selector)); + } + + return { + engine: engine, + select: select, + match: engine.match + }; +})(Slick); + +// Restore globals. +window.Slick = Prototype._original_property; +delete Prototype._original_property; From 24348920b204ad8f7fb465a07d7b207301dd197e Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 2 May 2010 19:44:12 +0200 Subject: [PATCH 224/502] Fix Sizzle wrapper. --- vendor/sizzle/selector_engine.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index f460f3352..08edc4faa 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -16,16 +16,11 @@ Prototype.Selector = (function(engine) { function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - - function filter(elements, selector) { - return extend(engine.matches(selector, elements)); - } - + return { engine: engine, select: select, - match: match, - filter: filter + match: match }; })(Sizzle); From 38e85b86dd5d218fe735afab969b87ac58983b77 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 4 May 2010 10:33:40 -0500 Subject: [PATCH 225/502] Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 2 +- test/unit/fixtures/layout.html | 12 ++++++++++++ test/unit/layout_test.js | 8 ++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4b4032aa2..dc415ca9b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) + Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index 37e2dccc3..9f1d026cc 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -25,7 +25,7 @@ // Non-IE browsers will always return pixels if possible. // (We use parseFloat instead of parseInt because Firefox can return // non-integer pixel values.) - if ((/^\d+(\.\d+)?(px)?$/i).test(value)) { + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { return window.parseFloat(value); } diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html index 5921e38ee..80c615b6d 100644 --- a/test/unit/fixtures/layout.html +++ b/test/unit/fixtures/layout.html @@ -1,3 +1,15 @@ +
      +

      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

      +
      + + +
      diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 278afee74..dbdeb8b7b 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -48,6 +48,14 @@ new Test.Unit.Runner({ this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, + 'test layout on elements with negative margins': function() { + var layout = $('box_with_negative_margins').getLayout(); + + this.assertEqual(-10, layout.get('margin-top') ); + this.assertEqual( -3, layout.get('margin-left') ); + this.assertEqual( 2, layout.get('margin-right')); + }, + 'test layout on elements with display: none and width: auto': function() { var layout = $('box3').getLayout(); From f372474e9d30b9e1a043232b3626c7d20bc9754e Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 5 May 2010 02:38:46 +0200 Subject: [PATCH 226/502] Fix Slick submodule protocol. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index e14ada56f..af46232a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -20,4 +20,4 @@ url = git://github.com/jeresig/sizzle.git [submodule "vendor/slick/repository"] path = vendor/slick/repository - url = http://github.com/mootools/slick.git + url = git://github.com/mootools/slick.git From 694f36dba270309bcbb852974ec738e6cf3ae721 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 7 May 2010 18:48:23 +0200 Subject: [PATCH 227/502] Update to latest version of pdoc. --- Rakefile | 4 +++- vendor/pdoc | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index d66430f91..de3867bc4 100755 --- a/Rakefile +++ b/Rakefile @@ -74,7 +74,9 @@ module PrototypeHelper :index_page => 'README.markdown', :syntax_highlighter => syntax_highlighter, :markdown_parser => :bluecloth, - :repository_url => "http://github.com/sstephenson/prototype/blob/#{current_head}/", + :src_code_href => proc { |file, line| + "http://github.com/sstephenson/prototype/blob/#{current_head}/#{file}#LID#{line}" + }, :pretty_urls => true, :bust_cache => false, :name => 'Prototype JavaScript Framework', diff --git a/vendor/pdoc b/vendor/pdoc index d1a6ef090..4d1ab30b8 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit d1a6ef090bc3d644e427b64fae15ee28eeb0a6a9 +Subproject commit 4d1ab30b80056d562991e02d17fc32f2ecf78983 From 5d454e4e5ea3f2da0350ed4f88f9f7eaa811cf97 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 7 May 2010 18:51:06 +0200 Subject: [PATCH 228/502] Make documentation pretty urls option false. The Rake task is targeted at users wanting to generate a local version of the documentation. Turning pretty urls off makes the documentation much easier to browser locally (using the file protocol). --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index de3867bc4..6bb9b7910 100755 --- a/Rakefile +++ b/Rakefile @@ -77,7 +77,7 @@ module PrototypeHelper :src_code_href => proc { |file, line| "http://github.com/sstephenson/prototype/blob/#{current_head}/#{file}#LID#{line}" }, - :pretty_urls => true, + :pretty_urls => false, :bust_cache => false, :name => 'Prototype JavaScript Framework', :short_name => 'Prototype', From b76ea83e0186296abc5aa984d260ad21159be584 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Fri, 7 May 2010 15:36:57 -0500 Subject: [PATCH 229/502] The second argument to Event.Handler callbacks is the target element when no selector is present, or the matching element when a selector is present. Callbacks are always bound to the original element. --- CHANGELOG | 10 +-- src/dom/event.js | 5 +- test/unit/event_handler_test.js | 99 +++++++++++++++++++++++++++ test/unit/fixtures/event_handler.html | 4 ++ 4 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 test/unit/event_handler_test.js create mode 100644 test/unit/fixtures/event_handler.html diff --git a/CHANGELOG b/CHANGELOG index dc415ca9b..ffc27be14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,12 @@ -Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) +* The second argument to Event.Handler callbacks is the target element when no selector is present, or the matching element when a selector is present. Callbacks are always bound to the original element. (sam) -Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) +* Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) -Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) +* Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) -Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) +* Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) + +* Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) *1.7_rc1* (April 1, 2010) diff --git a/src/dom/event.js b/src/dom/event.js index 023025ad8..b08f670ad 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -912,9 +912,8 @@ }, handleEvent: function(event) { - var element = this.selector ? event.findElement(this.selector) : - this.element; - if (element) this.callback.call(element, event, element); + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); } }); diff --git a/test/unit/event_handler_test.js b/test/unit/event_handler_test.js new file mode 100644 index 000000000..b10be134b --- /dev/null +++ b/test/unit/event_handler_test.js @@ -0,0 +1,99 @@ +new Test.Unit.Runner((function() { + function handle(selector, callback) { + if (!callback) { + callback = selector; + selector = false; + } + return new Event.Handler("container", "test:event", selector, callback); + } + + return { + testHandlersDoNothingIfStartHasNotBeenCalled: function() { + var fired = false; + this.handler = handle(function() { fired = true }); + + $("container").fire("test:event"); + this.assert(!fired); + }, + + testHandlersAreFiredWhenStartIsCalled: function() { + var fired = false; + this.handler = handle(function() { fired = true }); + + this.handler.start(); + this.assert(!fired); + $("container").fire("test:event"); + this.assert(fired); + }, + + testHandlersDoNotFireAfterStartingAndThenStopping: function() { + var fired = 0; + this.handler = handle(function() { fired++ }); + + this.handler.start(); + this.assertEqual(0, fired); + $("container").fire("test:event"); + this.assertEqual(1, fired); + this.handler.stop(); + $("container").fire("test:event"); + this.assertEqual(1, fired); + }, + + testHandlersWithoutSelectorsPassTheTargetElementToCallbacks: function() { + var span = $("container").down("span"); + this.handler = handle(function(event, element) { + this.assertEqual(span, element); + }.bind(this)); + + this.handler.start(); + span.fire("test:event"); + }, + + testHandlersWithSelectorsPassTheMatchedElementToCallbacks: function() { + var link = $("container").down("a"), span = link.down("span"); + this.handler = handle("a", function(event, element) { + this.assertEqual(link, element); + }.bind(this)); + + this.handler.start(); + span.fire("test:event"); + }, + + testHandlersWithSelectorsDoNotCallTheCallbackIfNoMatchingElementIsFound: function() { + var paragraph = $("container").down("p", 1), fired = false; + this.handler = handle("a", function(event, element) { fired = true }); + + this.handler.start(); + paragraph.fire("test:event"); + this.assert(!fired); + }, + + testHandlerCallbacksAreBoundToTheOriginalElement: function() { + var span = $("container").down("span"), element; + this.handler = handle(function() { element = this }); + + this.handler.start(); + span.fire("test:event"); + this.assertEqual($("container"), element); + }, + + testCallingStartMultipleTimesDoesNotInstallMultipleObservers: function() { + var fired = 0; + this.handler = handle(function() { fired++ }); + + this.handler.start(); + this.handler.start(); + $("container").fire("test:event"); + this.assertEqual(1, fired); + }, + + teardown: function() { + try { + this.handler.stop(); + } catch (e) { + } finally { + delete this.handler; + } + } + } +})()); diff --git a/test/unit/fixtures/event_handler.html b/test/unit/fixtures/event_handler.html new file mode 100644 index 000000000..e964b7e59 --- /dev/null +++ b/test/unit/fixtures/event_handler.html @@ -0,0 +1,4 @@ + From c9d6c3ee14747179a1038bc21055f2d6ccd492c4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 7 May 2010 17:56:16 -0500 Subject: [PATCH 230/502] Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) --- CHANGELOG | 10 +++++---- src/dom/dom.js | 50 ++++++++++++++++++++++++++++++++++++++++--- test/unit/dom_test.js | 30 ++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc415ca9b..804393a99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,12 @@ -Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) +* Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) -Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) +* Fix issue where `Element.Layout#get` would fail to interpret negative pixel values. (Sebastien Gruhier, Andrew Dupont) -Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) +* Fix bugs in layout.js. Add tests for `Element.Layout#toCSS`, `#toObject`, and `#toHash`. (RStankov, Andrew Dupont) -Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) +* Add `Element.Layout#toObject` and `Element.Layout.toHash`. (Andrew Dupont) + +* Make `Element.Layout#toCSS` return camelized property names, as expected by `Element.setStyle`. [#1021 state:resolved] (njakobsen, Andrew Dupont) *1.7_rc1* (April 1, 2010) diff --git a/src/dom/dom.js b/src/dom/dom.js index 75958e6d4..55c74d21d 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -189,6 +189,19 @@ if (!Node.ELEMENT_NODE) { Element.idCounter = 1; Element.cache = { }; +// Performs cleanup on an element before it is removed from the page. +// See `Element#purge`. +function purgeElement(element) { + // Must go first because it relies on Element.Storage. + Element.stopObserving(element); + + var uid = element._prototypeUID; + if (uid) { + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + /** * mixin Element.Methods * @@ -449,6 +462,11 @@ Element.Methods = { * Note that this method allows seamless content update of table related * elements in Internet Explorer 6 and beyond. * + * Any nodes replaced with `Element.update` will first have event + * listeners unregistered and storage keys removed. This frees up memory + * and prevents leaks in certain versions of Internet Explorer. (See + * [[Element.purge]]). + * * ##### Examples * * language: html @@ -551,7 +569,14 @@ Element.Methods = { function update(element, content) { element = $(element); - + + // Purge the element's existing contents of all storage keys and + // event listeners, since said content will be replaced no matter + // what. + var descendants = Element.select(element, '*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + if (content && content.toElement) content = content.toElement(); @@ -3715,8 +3740,8 @@ Element.addMethods({ uid = 0; } else { if (typeof element._prototypeUID === "undefined") - element._prototypeUID = [Element.Storage.UID++]; - uid = element._prototypeUID[0]; + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; } if (!Element.Storage[uid]) @@ -3786,5 +3811,24 @@ Element.addMethods({ } } return Element.extend(clone); + }, + + /** + * Element.purge(@element) -> null + * + * Removes all event listeners and storage keys from an element. + * + * To be used just before removing an element from the page. + **/ + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); + + var descendants = Element.select(element, '*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; } }); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 2b406083a..00f596d0d 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1501,6 +1501,36 @@ new Test.Unit.Runner({ this.assert(deepClone.firstChild); this.assertEqual('SPAN', deepClone.firstChild.nodeName.toUpperCase()); this.assert(!deepClone.down('span')._prototypeUID); + }, + + testElementPurge: function() { + var element = new Element('div'); + element.store('foo', 'bar'); + + var uid = element._prototypeUID; + this.assert(uid in Element.Storage, "newly-created element's uid should exist in `Element.Storage`"); + + element.purge(); + + this.assert(!(uid in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); + this.assert(!(Object.isNumber(element._prototypeUID)), "purged element's UID should no longer exist in `Element.Storage`"); + + // Should purge elements replaced via innerHTML. + var parent = new Element('div'); + var child = new Element('p').update('lorem ipsum'); + + parent.insert(child); + child.store('foo', 'bar'); + child.observe('test:event', function(event) { event.stop(); }); + var childUID = child._prototypeUID; + + parent.update(""); + + // At this point, `child` should have been purged. + this.assert(!(childUID in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); + + var event = child.fire('test:event'); + this.assert(!event.stopped, "fired event should not have been stopped"); } }); From feba59b1296cb9b22d0ff1439fc47a3d241b15d1 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 8 May 2010 03:09:25 +0200 Subject: [PATCH 231/502] doc: Minor edits. --- src/lang/array.js | 2 +- src/lang/regexp.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lang/array.js b/src/lang/array.js index f9ff92f47..7bdb9036d 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -1,5 +1,5 @@ /** section: Language, related to: Array - * $A(iterable) -> actualArray + * $A(iterable) -> Array * * Accepts an array-like collection (anything with numeric indices) and returns * its equivalent as an actual [[Array]] object. This method is a convenience diff --git a/src/lang/regexp.js b/src/lang/regexp.js index fd7c04a99..c1663408b 100644 --- a/src/lang/regexp.js +++ b/src/lang/regexp.js @@ -4,10 +4,12 @@ * Extensions to the built-in `RegExp` object. **/ -/** alias of: RegExp#test +/** * RegExp#match(str) -> Boolean + * - str (String): a string against witch to match the regular expression. * - * Return true if string matches the regular expression, false otherwise. + * Alias of the native `RegExp#test` method. Returns `true` + * if `str` matches the regular expression, `false` otherwise. **/ RegExp.prototype.match = RegExp.prototype.test; From d9aee20322172ca203c844dc1df75abff2569aba Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Tue, 11 May 2010 16:24:37 +0200 Subject: [PATCH 232/502] Refactor selector engine wrapper and add convenience helper methods to simplify implementation by third parties. --- Rakefile | 2 +- src/dom.js | 2 +- src/dom/selector.js | 104 ++++++++++++++-------- src/selector_engine.js | 21 ++--- vendor/legacy_selector/selector_engine.js | 11 +-- vendor/nwmatcher/selector_engine.js | 22 ++--- vendor/sizzle/selector_engine.js | 21 ++--- vendor/slick/selector_engine.js | 19 ++-- 8 files changed, 101 insertions(+), 101 deletions(-) diff --git a/Rakefile b/Rakefile index 6bb9b7910..19fa1969c 100755 --- a/Rakefile +++ b/Rakefile @@ -151,7 +151,7 @@ module PrototypeHelper return if name == DEFAULT_SELECTOR_ENGINE || !name submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) - + return submodule_path if name === "legacy_selector" get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" diff --git a/src/dom.js b/src/dom.js index b54f165b9..79d6924d1 100644 --- a/src/dom.js +++ b/src/dom.js @@ -20,9 +20,9 @@ **/ //= require "dom/dom" -//= require //= require "dom/layout" //= require "dom/selector" +//= require //= require "dom/form" //= require "dom/event" diff --git a/src/dom/selector.js b/src/dom/selector.js index 78cb90fba..61e21afda 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -95,44 +95,44 @@ window.$$ = function() { * the choosen selector engine (Sizzle by default). * **/ +Prototype.Selector = (function() { + + /** + * Prototype.Selector.select(expression[, root = document]) -> [Element...] + * - expression (String): A CSS selector. + * - root (Element | document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches `root` for elements that match the provided CSS selector and returns an + * array of extended [[Element]] objects. + **/ + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } -// Implementation provided by selector engine. - -/** - * Prototype.Selector.select(expression[, root = document]) -> [Element...] - * - expression (String): A CSS selector. - * - root (Element | document): A "scope" to search within. All results will - * be descendants of this node. - * - * Searches `root` for elements that match the provided CSS selector and returns an - * array of extended [[Element]] objects. -**/ - -// Implementation provided by selector engine. - -/** - * Prototype.Selector.match(element, expression) -> Boolean - * - element (Element): a DOM element. - * - expression (String): A CSS selector. - * - * Tests whether `element` matches the CSS selector. -**/ - -// Implementation provided by selector engine. + /** + * Prototype.Selector.match(element, expression) -> Boolean + * - element (Element): a DOM element. + * - expression (String): A CSS selector. + * + * Tests whether `element` matches the CSS selector. + **/ + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } -/** - * Prototype.Selector.find(elements, expression[, index = 0]) -> Element - * - elements (Enumerable): a collection of DOM elements. - * - expression (String): A CSS selector. - * - index: Numeric index of the match to return, defaults to 0. - * - * Filters the given collection of elements with `expression` and returns the - * first matching element (or the `index`th matching element if `index` is - * specified). -**/ -if (!Prototype.Selector.find) { - Prototype.Selector.find = function(elements, expression, index) { - if (Object.isUndefined(index)) index = 0; + /** + * Prototype.Selector.find(elements, expression[, index = 0]) -> Element + * - elements (Enumerable): a collection of DOM elements. + * - expression (String): A CSS selector. + * - index: Numeric index of the match to return, defaults to 0. + * + * Filters the given collection of elements with `expression` and returns the + * first matching element (or the `index`th matching element if `index` is + * specified). + **/ + function find(elements, expression, index) { + index = index || 0; var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; for (i = 0; i < length; i++) { @@ -141,5 +141,33 @@ if (!Prototype.Selector.find) { } } } -} - + + /** + * Prototype.Selector.extendElements(elements) -> Enumerable + * - elements (Enumerable): a collection of DOM elements. + * + * If necessary, extends the elements contained in `elements` + * and returns `elements` untouched. This is provided as a + * convenience method for selector engine wrapper implementors. + **/ + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + /** alias of: Element.extend + * Prototype.Selector.extendElement(element) -> Element + **/ + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); diff --git a/src/selector_engine.js b/src/selector_engine.js index 45ba30ce0..46e296f78 100644 --- a/src/selector_engine.js +++ b/src/selector_engine.js @@ -1,27 +1,20 @@ Prototype._original_property = window.Sizzle; //= require "sizzle" -Prototype.Selector = (function(engine) { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) { - Element.extend(elements[i]); - } - return elements; - } +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; function select(selector, scope) { - return extend(engine(selector, scope || document)); + return extendElements(engine(selector, scope || document)); } function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - - return { - engine: engine, - select: select, - match: match - }; + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; })(Sizzle); // Restore globals. diff --git a/vendor/legacy_selector/selector_engine.js b/vendor/legacy_selector/selector_engine.js index cf6287508..3c5cf305f 100644 --- a/vendor/legacy_selector/selector_engine.js +++ b/vendor/legacy_selector/selector_engine.js @@ -1,6 +1,6 @@ //= require "repository/legacy_selector" -Prototype.Selector = (function(engine) { +;(function(engine) { function select(selector, scope) { return engine.findChildElements(scope || document, [selector]); } @@ -9,10 +9,7 @@ Prototype.Selector = (function(engine) { return !!engine.findElement([element], selector); } - return { - engine: engine, - select: select, - match: match, - filter: engine.matchElements - }; + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; })(Prototype.LegacySelector); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 9b0fae991..a976c508e 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,20 +1,16 @@ Prototype._original_property = window.NW; //= require "repository/src/nwmatcher" -Prototype.Selector = (function(engine) { - var select = engine.select; - - if (Element.extend !== Prototype.K) { - select = function select(selector, scope) { - return engine.select(selector, scope, Element.extend); - }; +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine.select(selector, scope)); } - - return { - engine: engine, - select: select, - match: engine.match - }; + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = engine.match; })(NW.Dom); // Restore globals. diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index 08edc4faa..8100dd59e 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,27 +1,20 @@ Prototype._original_property = window.Sizzle; //= require "repository/sizzle" -Prototype.Selector = (function(engine) { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) { - Element.extend(elements[i]); - } - return elements; - } +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; function select(selector, scope) { - return extend(engine(selector, scope || document)); + return extendElements(engine(selector, scope || document)); } function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - - return { - engine: engine, - select: select, - match: match - }; + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; })(Sizzle); // Restore globals. diff --git a/vendor/slick/selector_engine.js b/vendor/slick/selector_engine.js index f49e02f1d..ddfaba0da 100644 --- a/vendor/slick/selector_engine.js +++ b/vendor/slick/selector_engine.js @@ -2,23 +2,16 @@ Prototype._original_property = window.Slick; //= require "repository/Source/Slick.Parser.js" //= require "repository/Source/Slick.Finder.js" -Prototype.Selector = (function(engine) { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) { - Element.extend(elements[i]); - } - return elements; - } +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; function select(selector, scope) { - return extend(engine.search(scope || document, selector)); + return extendElements(engine.search(scope || document, selector)); } - return { - engine: engine, - select: select, - match: engine.match - }; + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = engine.match; })(Slick); // Restore globals. From 9a752d70d97a095202c764ded3fb2086007102db Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 12 May 2010 10:50:09 -0500 Subject: [PATCH 233/502] Optimize element purging. (RStankov, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/dom.js | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index af9d3d2c5..facb515a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Optimize element purging. (RStankov, Andrew Dupont) + * Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) * The second argument to Event.Handler callbacks is the target element when no selector is present, or the matching element when a selector is present. Callbacks are always bound to the original element. (sam) diff --git a/src/dom/dom.js b/src/dom/dom.js index 55c74d21d..eb1783040 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -192,11 +192,10 @@ Element.cache = { }; // Performs cleanup on an element before it is removed from the page. // See `Element#purge`. function purgeElement(element) { - // Must go first because it relies on Element.Storage. - Element.stopObserving(element); - var uid = element._prototypeUID; if (uid) { + // Must go first because it relies on Element.Storage. + Element.stopObserving(element); element._prototypeUID = void 0; delete Element.Storage[uid]; } @@ -573,7 +572,7 @@ Element.Methods = { // Purge the element's existing contents of all storage keys and // event listeners, since said content will be replaced no matter // what. - var descendants = Element.select(element, '*'), + var descendants = element.getElementsByTagName('*'), i = descendants.length; while (i--) purgeElement(descendants[i]); @@ -3824,7 +3823,7 @@ Element.addMethods({ if (!(element = $(element))) return; purgeElement(element); - var descendants = Element.select(element, '*'), + var descendants = element.getElementsByTagName('*'), i = descendants.length; while (i--) purgeElement(descendants[i]); From e9f1e2f34741b83e8a2b0c3ef3805aca99c82984 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 12 May 2010 11:00:39 -0500 Subject: [PATCH 234/502] Prune dead namespace attribute selector tests --- test/unit/selector_test.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index 2d91199a9..56c730cbf 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -95,11 +95,6 @@ new Test.Unit.Runner({ this.assertEnumEqual([$('attr_with_dash')], $$('[foo-bar]'), "attribute with hyphen"); }, - testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() { - this.assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]')); - this.assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]')); - }, - testSelectorWithTagNameAndNegatedAttributeValue: function() { this.assertEnumEqual([], $$('a[href!="#"]')); }, @@ -171,17 +166,6 @@ new Test.Unit.Runner({ // AND NOW COME THOSE NEW TESTS AFTER ANDREW'S REWRITE! - testSelectorWithNamespacedAttributes: function() { - if (Prototype.BrowserFeatures.XPath) { - this.assertUndefined(new Selector('html[xml:lang]').xpath); - this.assertUndefined(new Selector('body p[xml:lang]').xpath); - } else - this.info("Could not test XPath bypass: no XPath to begin with!"); - - this.assertElementsMatch($$('[xml:lang]'), 'html', '#item_3'); - this.assertElementsMatch($$('*[xml:lang]'), 'html', '#item_3'); - }, - testSelectorWithChild: function() { this.assertEnumEqual($('link_1', 'link_2'), $$('p.first > a')); this.assertEnumEqual($('father', 'uncle'), $$('div#grandfather > div')); @@ -336,8 +320,8 @@ new Test.Unit.Runner({ }, testCommasFor$$: function() { - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,*[xml:lang="es-us"] , #troubleForm')); - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '*[xml:lang="es-us"] , #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,#item_3 , #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '#item_3 , #troubleForm')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]')); }, From 0ce5f4fad7cbb35f70d73d2abc187e94e9b9011c Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Wed, 12 May 2010 11:01:35 -0500 Subject: [PATCH 235/502] Update copyright --- LICENSE | 2 +- src/prototype.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 61e491823..05789cf01 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2008 Sam Stephenson +Copyright (c) 2005-2010 Sam Stephenson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/prototype.js b/src/prototype.js index 024599085..3be377568 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -1,5 +1,5 @@ /* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %> - * (c) 2005-2009 Sam Stephenson + * (c) 2005-2010 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ From 3e7bbca4b3fdf6cc8c6bd4a6f8084b9b2abfdfb6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 12 May 2010 12:50:03 -0500 Subject: [PATCH 236/502] Remove redefinition of `Element#cumulativeOffset` when `getBoundingClientRect` is present, as it seems to give inaccurate results. (Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 9 --------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index facb515a4..0dff3797e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Remove redefinition of `Element#cumulativeOffset` when `getBoundingClientRect` is present, as it seems to give inaccurate results. (Andrew Dupont) + * Optimize element purging. (RStankov, Andrew Dupont) * Add `Element.purge` for cleaning up event listeners and element storage keys on elements that will be removed from the page. Make `Element.update` perform similar cleanup automatically. (Andrew Dupont, Tobie Langel) diff --git a/src/dom/layout.js b/src/dom/layout.js index 9f1d026cc..317295e63 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -971,15 +971,6 @@ return new Element.Offset(rect.left - docEl.clientLeft, rect.top - docEl.clientTop); }, - - cumulativeOffset: function(element) { - element = $(element); - if (isDetached(element)) return new Element.Offset(0, 0); - - var docOffset = $(document.body).viewportOffset(), - elementOffset = element.viewportOffset(); - return elementOffset.relativeTo(docOffset); - }, positionedOffset: function(element) { element = $(element); From 339bc8c142c91a45aee464a484540bfa2bb6a674 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 12 May 2010 13:06:40 -0500 Subject: [PATCH 237/502] Bump version number. --- CHANGELOG | 2 ++ src/constants.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0dff3797e..7803fd3b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +*1.7_rc2* (May 12, 2010) + * Remove redefinition of `Element#cumulativeOffset` when `getBoundingClientRect` is present, as it seems to give inaccurate results. (Andrew Dupont) * Optimize element purging. (RStankov, Andrew Dupont) diff --git a/src/constants.yml b/src/constants.yml index 9728c12cd..14db9902b 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.7_rc1 +PROTOTYPE_VERSION: 1.7_rc2 From 876ece6139d38cd996688c9bca030e4ba9366ecc Mon Sep 17 00:00:00 2001 From: Duncan Beevers Date: Wed, 12 May 2010 18:48:22 -0700 Subject: [PATCH 238/502] Serialize form elements to string in the order in which they appear in the DOM --- src/dom/form.js | 31 ++++++++++++++++++++----------- test/unit/fixtures/form.html | 11 +++++++++++ test/unit/form_test.js | 8 ++++++++ 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index 861d3ce9a..62d8bf072 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -104,25 +104,34 @@ var Form = { // default true, as that's the new preferred approach. if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; - - var data = elements.inject({ }, function(result, element) { + var key, value, submitted = false, submit = options.submit, accumulator, initial; + + if (options.hash) { + initial = {}; + accumulator = function(result, key, value) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } else result[key] = value; + return result; + }; + } else { + initial = ''; + accumulator = function(result, key, value) { + return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value); + } + } + + return elements.inject(initial, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - // a key is already present; construct an array of values - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; + result = accumulator(result, key, value); } } return result; }); - - return options.hash ? data : Object.toQueryString(data); } }; diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index 9ac9a0f0c..c61f265ba 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -106,3 +106,14 @@
      + + + + + + + + + + + diff --git a/test/unit/form_test.js b/test/unit/form_test.js index f80044f12..22b1d0a43 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -280,6 +280,14 @@ new Test.Unit.Runner({ this.assertEqual('', $('form_with_file_input').serialize()); }, + testFormSerializeWithDuplicateNames: function() { + this.assertEqual("fact=sea-wet&opinion=sea-cold&fact=sun-hot&opinion=sun-ugly", $('form_with_duplicate_input_names').serialize(false)); + }, + + testFormSerializeURIEncodesInputs: function() { + this.assertEqual("user%5Bwristbands%5D%5B%5D%5Bnickname%5D=H%C3%A4sslich", $('form_with_inputs_needing_encoding').serialize(false)); + }, + testFormMethodsOnExtendedElements: function() { var form = $('form'); this.assertEqual(Form.serialize('form'), form.serialize()); From 59569f476b0b2e81c623dfdd36b21ba253cfb513 Mon Sep 17 00:00:00 2001 From: Duncan Beevers Date: Wed, 12 May 2010 18:49:19 -0700 Subject: [PATCH 239/502] Parameters provided to Ajax Request preserve ordering --- src/ajax/base.js | 4 +--- src/ajax/request.js | 12 +++++++----- test/unit/ajax_test.js | 13 +++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/ajax/base.js b/src/ajax/base.js index 1862d073b..40abc436e 100644 --- a/src/ajax/base.js +++ b/src/ajax/base.js @@ -14,9 +14,7 @@ Ajax.Base = Class.create({ this.options.method = this.options.method.toLowerCase(); - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) + if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); diff --git a/src/ajax/request.js b/src/ajax/request.js index b59f3cf0e..44263a1e1 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -179,17 +179,17 @@ Ajax.Request = Class.create(Ajax.Base, { request: function(url) { this.url = url; this.method = this.options.method; - var params = Object.clone(this.options.parameters); + var params = Object.isString(this.options.parameters) ? + this.options.parameters : + Object.toQueryString(this.options.parameters); if (!['get', 'post'].include(this.method)) { // simulate other verbs over post - params['_method'] = this.method; + params += (params ? '&' : '') + "_method=" + this.method; this.method = 'post'; } - this.parameters = params; - - if (params = Object.toQueryString(params)) { + if (params) { // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; @@ -197,6 +197,8 @@ Ajax.Request = Class.create(Ajax.Base, { params += '&_='; } + this.parameters = params.toQueryParams(); + try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); diff --git a/test/unit/ajax_test.js b/test/unit/ajax_test.js index f1f530e24..4b03a2b40 100644 --- a/test/unit/ajax_test.js +++ b/test/unit/ajax_test.js @@ -337,6 +337,19 @@ new Test.Unit.Runner({ } }, + testParametersStringOrderIsPreserved: function() { + if (this.isRunningFromRake) { + new Ajax.Request("/inspect", extendDefault({ + parameters: "cool=1&bad=2&cool=3&bad=4", + method: 'post', + onComplete: function(transport) { + var body_without_wart = transport.responseJSON.body.match(/((?:(?!&_=$).)*)/)[1]; + this.assertEqual("cool=1&bad=2&cool=3&bad=4", body_without_wart); + }.bind(this) + })); + } + }, + testIsSameOriginMethod: function() { var isSameOrigin = Ajax.Request.prototype.isSameOrigin; this.assert(isSameOrigin.call({ url: '/foo/bar.html' }), '/foo/bar.html'); From 6c184a0a1a50fdd58e2839f164877c19b0d7c694 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 14 May 2010 10:47:58 -0500 Subject: [PATCH 240/502] Ensure we clean up after ourselves for all `width` and `height` computations in `Element.Layout`. (Sam Stephenson, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7803fd3b0..8f9051737 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure we clean up after ourselves for all `width` and `height` computations in `Element.Layout`. (Sam Stephenson, Andrew Dupont) + *1.7_rc2* (May 12, 2010) * Remove redefinition of `Element#cumulativeOffset` when `getBoundingClientRect` is present, as it seems to give inaccurate results. (Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index 317295e63..c7bdc697f 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -439,8 +439,11 @@ 'height': function(element) { if (!this._preComputing) this._begin(); - var bHeight = this.get('border-box-height'); - if (bHeight <= 0) return 0; + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) { + if (!this._preComputing) this._end(); + return 0; + } var bTop = this.get('border-top'), bBottom = this.get('border-bottom'); @@ -457,7 +460,10 @@ if (!this._preComputing) this._begin(); var bWidth = this.get('border-box-width'); - if (bWidth <= 0) return 0; + if (bWidth <= 0) { + if (!this._preComputing) this._end(); + return 0; + } var bLeft = this.get('border-left'), bRight = this.get('border-right'); From 6a584d23899ea3422162e1cc13905f9a638fac1d Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 15 May 2010 15:54:26 +0200 Subject: [PATCH 241/502] Update pdoc. --- Rakefile | 17 ++++++++++++----- doc_assets/images/header-logo-small.png | Bin 0 -> 4123 bytes doc_assets/images/header-stripe-small.png | Bin 0 -> 1645 bytes vendor/pdoc | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 doc_assets/images/header-logo-small.png create mode 100644 doc_assets/images/header-stripe-small.png diff --git a/Rakefile b/Rakefile index 19fa1969c..62bec7ece 100755 --- a/Rakefile +++ b/Rakefile @@ -67,24 +67,31 @@ module PrototypeHelper def self.build_doc_for(file) rm_rf(DOC_DIR) mkdir_p(DOC_DIR) - + index_header = < + + Prototype JavaScript Framework API + + +EOF PDoc.run({ :source_files => Dir[File.join('src', '**', '*.js')], :destination => DOC_DIR, :index_page => 'README.markdown', :syntax_highlighter => syntax_highlighter, :markdown_parser => :bluecloth, - :src_code_href => proc { |file, line| - "http://github.com/sstephenson/prototype/blob/#{current_head}/#{file}#LID#{line}" + :src_code_href => proc { |obj| + "http://github.com/sstephenson/prototype/blob/#{hash}/#{obj.file}#LID#{obj.line_number}" }, :pretty_urls => false, :bust_cache => false, :name => 'Prototype JavaScript Framework', :short_name => 'Prototype', :home_url => 'http://prototypejs.org', - :doc_url => 'http://prototypejs.org/doc', :version => PrototypeHelper::VERSION, - :copyright_notice => 'This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.' + :index_header => index_header, + :footer => 'This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.', + :assets => 'doc_assets' }) end diff --git a/doc_assets/images/header-logo-small.png b/doc_assets/images/header-logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..f377977fb2992431077fac50569117e834ea2fe0 GIT binary patch literal 4123 zcmV+$5ajQPP)Er%q3Ebi3?(gu~f+|<{`v5`)AW|R z?f+N+U7Yj*YX70c^@FkSB76LLtnu;e;@R2S=jiC^>g(Iw-RkM;=hDP1f&1Fv<*Ul{ zN|E>T^z`iP?GkhT=H=tm)YSB-cFDa>VdU`}Fhh{cRQXo@n3T;6sl2 zSD5u0b^Nl<^!B=z=iJlu?BX1G{Os%O@A2}Fy7p?N_**nU?^78TYvx@%n;qL0=-rwK6(CP_o{>awoR+{xa?d>;)`se59<>utq*x2#%@!;X%)z;VQ>gaK&@)B(R;^^yuuJG-+mW;Xg_O5{P z@$mNW=+)KL#>dCQ!^F|i(bCe=@bU4{($eMS=J)sb^z`-i_xJSm_4W4l_V)MF)6>$@ z)6dY*(9h7&(9zM+($LY-&(F~C@bS^m(bCh?(9h4&($Uk>)6vk-(b3S*(9h4%&(hM- z)YH?@(a_V>)ba81($Ue<)6&z_)9~=`_4W4D)70GE-N(qt?Ca?C@8`h4!0_?#@$vAx zySvlU($mw@$j8X#=I7hn-0$%4^6~NrXa4x_;p5}v%*@Qz*VkH@`ta`Q-r(To>+$I8 z@c;hyq{j99_wfJu>wvHDXQA_iwD;rg_4@ep@9gUD?(M(7zw5rD*Wc;!;@G^ryz=t% z&(F{4>FUtX(BtIf<>uz(<>l(?>!C!;T>tydI$o<5%!RUeNFy@HVZAiETPb8YH0~t{V?8^uT6rPMH*p3+Uhf?TgE3hOZ_|S-oih_tvL`BhwWS?Y|_Cbs&h8D$~ z5syy$SOt>lBy)=LVMtNQ77JrOzt5I$wD|B}bNBmwSy@?4O=TI4$`WQQ`GOhf=_PAi z!qh9RteTp#lCqi-I4R-82+-7$8dR()^K&oEPfjuN=c5nU@)0bSwANc#$HeE0i=Y3` z14JiI?C8AJnr5-kc6pqZRcByJ70`X?y=3EZ&BVk zdBh_=gF3Zo6MrY5TW`I~66BW88g2Y?Axyx!HWB$^`NH!Z@qC9_S5BWkJIXkH`bw#TvGA7C zvI*lCVt0+@XMZ+G3f@+*t64tKtx zbolsboEzcmvV8=drRDtWTNrjt@ac2I#}CH|S0d;SBj|jt+?dvZ-72(P_+=P)PCN?@ z+g(V4n7bW?v!tcN5%{It7>{g|hhe+_tu^ep%_tI2{r0%@EtD!{ubH)fShjGr6El)8 z3Z|&6ba;ezm_`a!Z=km=Pk`@u` zlb*i4CgNW&fBf>R%V(9ZaoJ6Jwr@z}T%=M% zuSZyZG_v(K^ntSuUzdmDI2mM!`~0vc;gyFIIUM#L0&IN#xhN+vn!tiyTfkJYw7LHvrhWdWATK1X@FJNEYD;n}|P5_fzAJljPf94{ik&@3I7?z@4ro=8su`g^`a z66bq2GAYE7)4UR2#_9c?vFRGJ6$JOeF$mwoX$}qx*@tg)bO^wAhcq{T9ze8nf`d=Q z!RY9?9$)4d@iBLHgdsQ>jmyPe#G<&k?^lx(#Ex^J3mDrthG2(~h4}iAc6?7r+==E8 z7s4~R+3FG2J(%rWPO!lsb|KvyPI$t44bq(S1LM$>;q1oOageR^^4Z*%BjBDbf0aA$ zh3&}pHf)N69I-bn5Ey$oKz9Ty$94F=IQBjE>*IXWbK;(8>*W}hoASbYuD(Ct@Fcgq z!2jD8((Rc)!q2}?mf;CEZ?4Cuee(WJ?)Zn0dH)srmtJ|whFElie{&M)`S#E0iA!(X z+(vlagyP#WLwWb{1W9TF zexLWuPVPV1kU2ITY~Qva@&Z|+rEcWb$@mlrm6|}GeS7fx z+mE$vekurmc2y`v@ob2Iz(dHE3Y_EXx=}_!ZVJATN87SzbAS5~GVJl(c@(nE6j8jz z_@BTqmEr+o6%-|q|K=!u|4oTR!n^tRke)}&*mitp4C_JE63G<8Gf>tdT9tq=UPU3> z7M7hi{_Hob7P~o#Fh#Z!_D9@#8;-S@AI|2kV5ibo+hR9~Tz$Z8X<&;4u27Z<5|Q}P zH*G-9j>Qu3Hco<#${^S5(cd=LNQLsIki=B%actb5A3iD%)=9ANF>zc58V#k7}Pem zo4%36YSJw-fFNh`M3ftCOQkL($VJ|XxZ4gHC5eQmK(VLWbLY+8$>o0Z5N;+O5GaDy zg{14GRNB^!pd}{rD7%e`xUaXj8~*i7J8H=wwkZ_FJilirCz7stHWC&qbLD}pnUEBv zP7ZJ-4@{W@YmglM8lE4uVft))uKmvOY>JX?Nl&md!{R0po|K4U!LywmkI7owFmB&YC6WtqbNzGGVWYmTsj}$2Pn&eflvr z3S+mfId;s<6lKUY7T%yOAdar~J}YmGxG@a?q?&ZYX>QbU3$ zQ`CP78#*9X#9ZzD$4Z2n;SzhVc2f(B0lYXzSFwfwxDz|5OsX$I@3jHJplDfQwTtj}Cu7%^r1ZPwtD6>9Cwe6mD$ia8r>v%9)CWWSEDK zSZa!|_ngcTbec0V3OX@N;SoQQ=2*L*Ge_iVPPw23boLTfZ1>r_)JAn|rp$wdhg8 zHIKXQZHkjlm^|h@I3nBgSN)2@?>ODMZmUv^8dSE|Z#g-I7ZoXK+pFi4%B$xt7x~#1 zV|ScJ5qGd+TfZVD;If|*Qz|PVs{B&vSL9csF{$->6AiW6q=G+_2_+!bL&9WIshDa# zOz2Q;>Y>-G`p;0PR-q51>rt)*43srW+OGzy1Sl)fzZkw$GB~H~hjZupWd~HOxO(o~ z<;#N=Aa}V>ud?hC`X@%&7ni`VtOhH21d86M%sMYC87i{+b1#*l=1W%>qLOoxBJ=7ca`ST6wix4k^?EL8Dw=tyNUZ zv*@8sO@5;Gi+p z>2!4!a$Q|56rfb6tI$ACU9C<7XL%jchZqDjO?8mcFsrDm)oAK~TZ?owwHjCl)`T{9 zEgTwp>uZ=pW1aQ@&2$=5FRgdbG(Ae|m8h-l(Lx7U!>6Z$)uWe6HNb;;U)9zC-dS~T z1r_S)L5;!&J>A=YPAavDsrKpVnFRBWSM|Quz{Kji^rkAkTBWY)qLox?wW>;`QdL!{ zP5P=X_?oCv!Po#_Bx>{}QQuXiZ)i~KyYvk(!k36%rRp*=?XR`j_K*H8 ZzyQC?_H;||rKJD>002ovPDHLkV1nUx@F@TQ literal 0 HcmV?d00001 diff --git a/doc_assets/images/header-stripe-small.png b/doc_assets/images/header-stripe-small.png new file mode 100644 index 0000000000000000000000000000000000000000..e104640637eb3fb19bba22b95067b0ca28c23a51 GIT binary patch literal 1645 zcmWlaYgiLk8pl%+6dJ5n1d%GK2v$okK}t)jZzMd-tVC<2vB)onGInaoTwlguS^OM*o%E_GF8XFr_temL*>zyJU5d^pvK z3ESM=R=G(e5_d&xRI*rCh%0`vleqW9N}I&ut&UczcdByKWf_OEB@tPw1KBb~VMcCt za&|`6(UJ$*nU# zc*jB*VAyK4QWV87jFn+fqfxNgAP^EIY+|P%_Kw2_7&n`74A<%OHk(ba)7xzh6Nc&a zfWX@rmNgkoAOx9lGtcoX$B_gnY6-kxfDA^|$ngSZ!t?+D=m9a3UZ;aZOduo(0%}BU zf*k+>E5i;94jUkdrf7yVWkfnG%j*Hruii>CViW{M3=;+&@c)!ZoaPl#5h+Ko=2ODHRe2tjADoeP^(6;xttA$=M>Q%By0se78*({vb$903 zM&iXR;d5U)&k|)(hvz!}F?;ctZj12y>%QqO+G6j-+h^v<#(B6Zu08%IkKxQEli4nj z?UJ;h+@NDQr7{n<;LJF zrR=RTuNljSUamtv>|fUEm#@h(we%P4Soy|jX^hhIyNuGu(G-&3n7uFU(7A_PY*T_) z-A%Bd=%XKh?-)!7sI#Yfp7K&mKi|>+Rp7ZZOXsu;ajtg#*qpU)j=T6WzWj=^`2mA( z4yp?qDH2A?*G$jkK9rN6k7Vg^x-NIt^~LD7b4Su;UAS{awd>uf&5MsrEPh|o`&o!j zZwNl4XrgZYlGDB2tw2`Nzur<*jR%x`5g_mLKd<;Wc4^&%ZzIj`$OBxp{-6H6A!hrc zMg47UqmSd_`vaC%N6CR(%}#i$QoCgqf2s})z&txt_t5C6mHOb-H4yalsaoh%H}lm_ zbKe*JaV0Nk@KM^8V{s65?d!t-{C7{%1N&nwHv_{FdoNbqb_sGH4GAB4yV71R^v9WjEHME?>OSGoeSUS{Ns(i8{xfxy$iKBVw&5!uEP9nrOtV(ZF%|mYmZA* z@4Y!Tuzfb9dg0EQq z%T%J-)p@dHApP1HJb!(CoOYk@!5817J$pFK%YViCx)gjA*FLSSKTZJ$%M&YN+LD&I zZ|KYq-n!K#rQPL?gXQv-(v9_Zi&Hg+liV*IXppa{_&1gJwV#vJ&vR2@X6BiOWi|5n zCI=K!^|#O|@0RYZYpR9t$BK2Mt~K{I{m}5`)Vf`x)%ErHS7UB(Y02NADbqMVO)PKwswlWszMy32w`G1|N~Qv`zXVNI_25yzvQN2$&_^RD6XE1B2#mb&gYUIV zH*dNvvfF;?ne257ypVoz>_DwX8K`XiS*^Yw&NtTAm(*%%om2zF+1*mr$64u5uA|)a zqoTfW+lpP2@;-fMC9eJ?wPU4;o^*E&d)39j1=&d z*E>?_)1f)ceD$ARNuDo&@JuGp@BQS%S(jhs{IxP{ gd|Wj>rFzXpf=BmFd?8Q!|A!(`L?=Yuh}>WCKZQolu>b%7 literal 0 HcmV?d00001 diff --git a/vendor/pdoc b/vendor/pdoc index 4d1ab30b8..42f9e9c4d 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 4d1ab30b80056d562991e02d17fc32f2ecf78983 +Subproject commit 42f9e9c4d9e746e43503e58ffa69ce602a862a15 From ff6cc3e4ebadae595c8af23a80914afb8c080df9 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 17 May 2010 19:46:17 -0500 Subject: [PATCH 242/502] The `client*` properties seem problematic in Safari. Removing them. --- src/dom/layout.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index c7bdc697f..0c864a58f 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -571,23 +571,19 @@ }, 'border-top': function(element) { - return Object.isNumber(element.clientTop) ? element.clientTop : - getPixelValue(element, 'borderTopWidth'); + return getPixelValue(element, 'borderTopWidth'); }, 'border-bottom': function(element) { - return Object.isNumber(element.clientBottom) ? element.clientBottom : - getPixelValue(element, 'borderBottomWidth'); + return getPixelValue(element, 'borderBottomWidth'); }, 'border-left': function(element) { - return Object.isNumber(element.clientLeft) ? element.clientLeft : - getPixelValue(element, 'borderLeftWidth'); + return getPixelValue(element, 'borderLeftWidth'); }, 'border-right': function(element) { - return Object.isNumber(element.clientRight) ? element.clientRight : - getPixelValue(element, 'borderRightWidth'); + return getPixelValue(element, 'borderRightWidth'); }, 'margin-top': function(element) { From 7a4a5c706a819037b40b72b67d6a691cac20d34a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 17 May 2010 19:55:05 -0500 Subject: [PATCH 243/502] Fix issue with retrieving border-box-(height|width) of hidden elements. [#1035 state:resolved] (Francois-Pierre Bouchard, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 10 ++++++++-- test/unit/layout_test.js | 14 ++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8f9051737..2b9bfbbc0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix issue with retrieving border-box-(height|width) of hidden elements. [#1035 state:resolved] (Francois-Pierre Bouchard, Andrew Dupont) + * Ensure we clean up after ourselves for all `width` and `height` computations in `Element.Layout`. (Sam Stephenson, Andrew Dupont) *1.7_rc2* (May 12, 2010) diff --git a/src/dom/layout.js b/src/dom/layout.js index 0c864a58f..6001091b3 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -493,11 +493,17 @@ }, 'border-box-height': function(element) { - return element.offsetHeight; + if (!this._preComputing) this._begin(); + var height = element.offsetHeight; + if (!this._preComputing) this._end(); + return height; }, 'border-box-width': function(element) { - return element.offsetWidth; + if (!this._preComputing) this._begin(); + var width = element.offsetWidth; + if (!this._preComputing) this._end(); + return width; }, 'margin-box-height': function(element) { diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index dbdeb8b7b..5900aaa8d 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -41,9 +41,10 @@ new Test.Unit.Runner({ this.assert(!isDisplayed($('box3')), 'box should be hidden'); - this.assertEqual(500, layout.get('width'), 'width'); - this.assertEqual(3, layout.get('border-right'), 'border-right'); - this.assertEqual(10, layout.get('padding-bottom'), 'padding-bottom'); + this.assertEqual(500, layout.get('width'), 'width'); + this.assertEqual( 3, layout.get('border-right'), 'border-right'); + this.assertEqual( 10, layout.get('padding-bottom'), 'padding-bottom'); + this.assertEqual(526, layout.get('border-box-width'), 'border-box-width'); this.assert(!isDisplayed($('box3')), 'box should still be hidden'); }, @@ -61,10 +62,11 @@ new Test.Unit.Runner({ this.assert(!isDisplayed($('box3')), 'box should be hidden'); - this.assertEqual(364, layout.get('width'), 'width'); + this.assertEqual(364, layout.get('width'), 'width'); this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); - this.assertEqual(3, layout.get('border-right'), 'border-top'); - this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); + this.assertEqual(390, layout.get('border-box-width'), 'border-box-width'); + this.assertEqual(3, layout.get('border-right'), 'border-top'); + this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); // Ensure that we cleaned up after ourselves. this.assert(!isDisplayed($('box3')), 'box should still be hidden'); From ec15b2c0a916725dc56c1d536dc383eaf109b97c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 May 2010 17:35:40 -0500 Subject: [PATCH 244/502] Ensure `Element.Layout` gives accurate measurements for percentages on all elements. Fix inaccurate measurements on `position: fixed` elements with percentages. [#1040 state:resolved] (Dan Popescu, Riccardo De Agostini, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 59 +++++++++++++++++++++++++--------- test/unit/fixtures/layout.html | 50 +++++++++++++++++++++++++++- test/unit/layout_test.js | 30 +++++++++++++++++ 4 files changed, 125 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2b9bfbbc0..c1abeee8f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure `Element.Layout` gives accurate measurements for percentages on all elements. Fix inaccurate measurements on `position: fixed` elements with percentages. [#1040 state:resolved] (Dan Popescu, Riccardo De Agostini, Andrew Dupont) + * Fix issue with retrieving border-box-(height|width) of hidden elements. [#1035 state:resolved] (Francois-Pierre Bouchard, Andrew Dupont) * Ensure we clean up after ourselves for all `width` and `height` computations in `Element.Layout`. (Sam Stephenson, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index 6001091b3..525890e4b 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -13,11 +13,13 @@ // getPixelValue("11px"); // Or like this: // getPixelValue(someElement, 'paddingTop'); - function getPixelValue(value, property) { + function getPixelValue(value, property, context) { + var element = null; if (Object.isElement(value)) { element = value; value = element.getStyle(property); } + if (value === null) { return null; } @@ -29,32 +31,54 @@ return window.parseFloat(value); } + var isPercentage = value.include('%'), isViewport = (context === document.viewport); + // When IE gives us something other than a pixel value, this technique // (invented by Dean Edwards) will convert it to pixels. - if (/\d/.test(value) && element.runtimeStyle) { + // + // (This doesn't work for percentage values on elements with `position: fixed` + // because those percentages are relative to the viewport.) + if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { var style = element.style.left, rStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = value || 0; value = element.style.pixelLeft; element.style.left = style; element.runtimeStyle.left = rStyle; - + return value; } // For other browsers, we have to do a bit of work. - if (value.include('%')) { + // (At this point, only percentages should be left; all other CSS units + // are converted to pixels by getComputedStyle.) + if (element && isPercentage) { + context = context || element.parentNode; var decimal = toDecimal(value); - var whole; - if (property.include('left') || property.include('right') || - property.include('width')) { - whole = $(element.parentNode).measure('width'); - } else if (property.include('top') || property.include('bottom') || - property.include('height')) { - whole = $(element.parentNode).measure('height'); + var whole = null; + var position = element.getStyle('position'); + + var isHorizontal = property.include('left') || property.include('right') || + property.include('width'); + + var isVertical = property.include('top') || property.include('bottom') || + property.include('height'); + + if (context === document.viewport) { + if (isHorizontal) { + whole = document.viewport.getWidth(); + } else if (isVertical) { + whole = document.viewport.getHeight(); + } + } else { + if (isHorizontal) { + whole = $(context).measure('width'); + } else if (isVertical) { + whole = $(context).measure('height'); + } } - return whole * decimal; + return (whole === null) ? 0 : whole * decimal; } // If we get this far, we should probably give up. @@ -213,7 +237,7 @@ * measurements, it's probably not worth it. **/ initialize: function($super, element, preCompute) { - $super(); + $super(); this.element = $(element); // nullify all properties keys @@ -282,6 +306,10 @@ var position = element.getStyle('position'), width = element.getStyle('width'); + + // Preserve the context in case we get a percentage value. + var context = (position === 'fixed') ? document.viewport : + element.parentNode; element.setStyle({ position: 'absolute', @@ -299,9 +327,9 @@ // (b) it has an explicitly-set width, instead of width: auto. // Either way, it means the element is the width it needs to be // in order to report an accurate height. - newWidth = getPixelValue(width); + newWidth = getPixelValue(element, 'width', context); } else if (width && (position === 'absolute' || position === 'fixed')) { - newWidth = getPixelValue(width); + newWidth = getPixelValue(element, 'width', context); } else { // If not, that means the element's width depends upon the width of // its parent. @@ -335,6 +363,7 @@ if (!(property in COMPUTATIONS)) { throw "Property not found."; } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html index 80c615b6d..7d123f6a0 100644 --- a/test/unit/fixtures/layout.html +++ b/test/unit/fixtures/layout.html @@ -139,4 +139,52 @@ left: 0; top: 0; } - \ No newline at end of file + + + + +
      +
      + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +
      +
      + + + + +
      +
      + Duis aute. +
      +
      + + + + +
      + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
      + + diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 5900aaa8d..329370078 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -105,6 +105,36 @@ new Test.Unit.Runner({ this.assertIdentical($('box6_parent'), $('box6').getOffsetParent()); }, + 'test layout on statically-positioned element with percentage width': function() { + var layout = $('box7').getLayout(); + + this.assertEqual(150, layout.get('width')); + }, + + 'test layout on absolutely-positioned element with percentage width': function() { + var layout = $('box8').getLayout(); + + this.assertEqual(150, layout.get('width')); + }, + + 'test layout on fixed-position element with percentage width': function() { + var viewportWidth = document.viewport.getWidth(); + var layout = $('box9').getLayout(); + + function assertNear(v1, v2, message) { + var abs = Math.abs(v1 - v2); + this.assert(abs <= 1, message); + } + + // With percentage widths, we'll occasionally run into rounding + // discrepancies. Assert that the values agree to within 1 pixel. + var vWidth = viewportWidth / 4, eWidth = $('box9').measure('width'); + assertNear.call(this, vWidth, eWidth, 'width (visible)'); + + $('box9').hide(); + assertNear.call(this, vWidth, $('box9').measure('width'), 'width (hidden)'); + }, + 'test #toCSS, #toObject, #toHash': function() { var layout = $('box6').getLayout(); var top = layout.get('top'); From e1d6a03815db4e3b4a4067bfd371acb5bfb9b032 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 2 Jul 2010 00:03:58 -0500 Subject: [PATCH 245/502] Fix accidental declaration of `purgeElement` as a global function. [#1089 state:resolved] --- CHANGELOG | 2 ++ src/dom/dom.js | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c1abeee8f..4c96cb08d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix accidental declaration of `purgeElement` as a global function. [#1089 state:resolved] (Viktor Kojouharov, Andrew Dupont) + * Ensure `Element.Layout` gives accurate measurements for percentages on all elements. Fix inaccurate measurements on `position: fixed` elements with percentages. [#1040 state:resolved] (Dan Popescu, Riccardo De Agostini, Andrew Dupont) * Fix issue with retrieving border-box-(height|width) of hidden elements. [#1035 state:resolved] (Francois-Pierre Bouchard, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index eb1783040..798f7ac0f 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -191,7 +191,7 @@ Element.cache = { }; // Performs cleanup on an element before it is removed from the page. // See `Element#purge`. -function purgeElement(element) { +Element._purgeElement = function(element) { var uid = element._prototypeUID; if (uid) { // Must go first because it relies on Element.Storage. @@ -568,6 +568,7 @@ Element.Methods = { function update(element, content) { element = $(element); + var purgeElement = Element._purgeElement; // Purge the element's existing contents of all storage keys and // event listeners, since said content will be replaced no matter @@ -3820,7 +3821,9 @@ Element.addMethods({ * To be used just before removing an element from the page. **/ purge: function(element) { - if (!(element = $(element))) return; + if (!(element = $(element))) return; + var purgeElement = Element._purgeElement; + purgeElement(element); var descendants = element.getElementsByTagName('*'), From aefb58db55acdedce49788cb12d0fcc9fc63d768 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 2 Jul 2010 00:05:44 -0500 Subject: [PATCH 246/502] Revert `Element.getHeight` and `Element.getWidth` to their previous behavior in order to ensure backward-compatibility. --- CHANGELOG | 2 ++ src/dom/layout.js | 69 +++++++++++++++++++++++++++++++------- test/unit/fixtures/dom.css | 4 --- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4c96cb08d..a63ada65c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Revert `Element.getHeight` and `Element.getWidth` to their previous behavior in order to ensure backward-compatibility. (Sam Stephenson, Andrew Dupont) + * Fix accidental declaration of `purgeElement` as a global function. [#1089 state:resolved] (Viktor Kojouharov, Andrew Dupont) * Ensure `Element.Layout` gives accurate measurements for percentages on all elements. Fix inaccurate measurements on `position: fixed` elements with percentages. [#1040 state:resolved] (Dan Popescu, Riccardo De Agostini, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index 525890e4b..ede75a1db 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -760,16 +760,31 @@ * Finds the computed width and height of `element` and returns them as * key/value pairs of an object. * - * This method returns correct values on elements whose display is set to - * `none` either in an inline style rule or in an CSS stylesheet. - * - * In order to avoid calling the method twice, you should consider caching - * the values returned in a variable as shown below. If you only need - * `element`'s width or height, consider using [[Element.getWidth]] or - * [[Element.getHeight]] instead. + * For backwards-compatibility, these dimensions represent the dimensions + * of the element's "border box" (including CSS padding and border). This + * is equivalent to the built-in `offsetWidth` and `offsetHeight` + * browser properties. * * Note that all values are returned as _numbers only_ although they are * _expressed in pixels_. + * + * ##### Caveats + * + * * If the element is hidden via `display: none` in CSS, this method will + * attempt to measure the element by temporarily removing that CSS and + * applying `visibility: hidden` and `position: absolute`. This gives + * the element dimensions without making it visible or affecting the + * positioning of surrounding elements — but may not give accurate + * results in some cases. [[Element.measure]] is designed to give more + * accurate results. + * + * * In order to avoid calling the method twice, you should consider + * caching the returned values in a variable, as shown in the example + * below. + * + * * For more complex use cases, use [[Element.measure]], which is able + * to measure many different aspects of an element's dimensions and + * offsets. * * ##### Examples * @@ -788,11 +803,41 @@ * // -> 100 **/ function getDimensions(element) { - var layout = $(element).getLayout(); - return { - width: layout.get('width'), - height: layout.get('height') - }; + element = $(element); + var display = Element.getStyle(element, 'display'); + + if (display && display !== 'none') { + return { width: element.offsetWidth, height: element.offsetHeight }; + } + + // All *Width and *Height properties give 0 on elements with + // `display: none`, so show the element temporarily. + var style = element.style; + var originalStyles = { + visibility: style.visibility, + position: style.position, + display: style.display + }; + + var newStyles = { + visibility: 'hidden', + display: 'block' + }; + + // Switching `fixed` to `absolute` causes issues in Safari. + if (originalStyles.position !== 'fixed') + newStyles.position = 'absolute'; + + Element.setStyle(element, newStyles); + + var dimensions = { + width: element.offsetWidth, + height: element.offsetHeight + }; + + Element.setStyle(element, originalStyles); + + return dimensions; } /** diff --git a/test/unit/fixtures/dom.css b/test/unit/fixtures/dom.css index f93a89c2f..029bf1129 100644 --- a/test/unit/fixtures/dom.css +++ b/test/unit/fixtures/dom.css @@ -39,10 +39,6 @@ div.style-test { margin-left: 1px } width: 20em; } -#dimensions-visible { - padding-top: 0.1em; -} - #dimensions-visible-pos-abs, #dimensions-display-none-pos-abs { position: absolute; From fd433df49d0e1019bdb7752a6b15d1451ef643a5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 21 Aug 2010 21:53:01 -0500 Subject: [PATCH 247/502] Change custom events implementation to use the `onlosecapture` event instead of the `onfilterchange` event for non-bubbling custom events in Internet Explorer. (John-David Dalton, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/event.js | 16 ++++++++-------- test/unit/event_test.js | 11 +++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a63ada65c..f50407a76 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Change custom events implementation to use the `onlosecapture` event instead of the `onfilterchange` event for non-bubbling custom events in Internet Explorer. (John-David Dalton, Andrew Dupont) + * Revert `Element.getHeight` and `Element.getWidth` to their previous behavior in order to ensure backward-compatibility. (Sam Stephenson, Andrew Dupont) * Fix accidental declaration of `purgeElement` as a global function. [#1089 state:resolved] (Viktor Kojouharov, Andrew Dupont) diff --git a/src/dom/event.js b/src/dom/event.js index b08f670ad..afd2452ff 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -684,7 +684,7 @@ // We observe two IE-proprietarty events: one for custom events that // bubble and one for custom events that do not bubble. element.attachEvent("ondataavailable", responder); - element.attachEvent("onfilterchange", responder); + element.attachEvent("onlosecapture", responder); } } else { var actualEventName = _getDOMEventName(eventName); @@ -798,7 +798,7 @@ element.removeEventListener("dataavailable", responder, false); else { element.detachEvent("ondataavailable", responder); - element.detachEvent("onfilterchange", responder); + element.detachEvent("onlosecapture", responder); } } else { // Ordinary event. @@ -816,13 +816,13 @@ /** * Event.fire(element, eventName[, memo[, bubble = true]]) -> Event - * - memo (?): Metadata for the event. Will be accessible through the - * event's `memo` property. - * - bubble (Boolean): Whether the event will bubble. + * - memo (?): Metadata for the event. Will be accessible to event + * handlers through the event's `memo` property. + * - bubble (Boolean): Whether the event should bubble. * * Fires a custom event of name `eventName` with `element` as its target. * - * Custom events must include a colon (`:`) in their names. + * Custom events **must** include a colon (`:`) in their names. **/ function fire(element, eventName, memo, bubble) { element = $(element); @@ -836,10 +836,10 @@ var event; if (document.createEvent) { event = document.createEvent('HTMLEvents'); - event.initEvent('dataavailable', true, true); + event.initEvent('dataavailable', bubble, true); } else { event = document.createEventObject(); - event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; } event.eventName = eventName; diff --git a/test/unit/event_test.js b/test/unit/event_test.js index 3e3416c29..ba90b9f69 100644 --- a/test/unit/event_test.js +++ b/test/unit/event_test.js @@ -222,6 +222,17 @@ new Test.Unit.Runner({ this.assert(event.stopped, "event.stopped should be true for an observer that calls stop"); span.stopObserving("test:somethingHappened"); }, + + testNonBubblingCustomEvent: function() { + var span = $('span'), outer = $('outer'), event; + + var outerRespondedToEvent = false; + outer.observe("test:bubbleEvent", function(e) { outerRespondedToEvent = true }); + span.fire("test:bubbleEvent", {}, false); + + this.assertEqual(false, outerRespondedToEvent, + 'parent element should not respond to non-bubbling event fired on child'); + }, testEventFindElement: function() { var span = $("span"), event; From c814fb7bb4c3e025f90cb08622a8fe74e3fbf646 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 22 Aug 2010 17:01:19 -0500 Subject: [PATCH 248/502] Ensure hidden absolutely-positioned elements are sized correctly when they have no explicit width set. [#1084 state:resolved] (Viktor Kojouharov, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 10 ++++++---- test/unit/fixtures/layout.html | 21 +++++++++++++++++++++ test/unit/layout_test.js | 9 ++++++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f50407a76..31368f88b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure hidden absolutely-positioned elements are sized correctly when they have no explicit width set. [#1084 state:resolved] (Viktor Kojouharov, Andrew Dupont) + * Change custom events implementation to use the `onlosecapture` event instead of the `onfilterchange` event for non-bubbling custom events in Internet Explorer. (John-David Dalton, Andrew Dupont) * Revert `Element.getHeight` and `Element.getWidth` to their previous behavior in order to ensure backward-compatibility. (Sam Stephenson, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index ede75a1db..0c15382b3 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -328,11 +328,13 @@ // Either way, it means the element is the width it needs to be // in order to report an accurate height. newWidth = getPixelValue(element, 'width', context); - } else if (width && (position === 'absolute' || position === 'fixed')) { + } else if (position === 'absolute' || position === 'fixed') { + // Absolute- and fixed-position elements' dimensions don't depend + // upon those of their parents. newWidth = getPixelValue(element, 'width', context); } else { - // If not, that means the element's width depends upon the width of - // its parent. + // Otherwise, the element's width depends upon the width of its + // parent. var parent = element.parentNode, pLayout = $(parent).getLayout(); newWidth = pLayout.get('width') - @@ -1081,4 +1083,4 @@ } }); } -})(); \ No newline at end of file +})(); diff --git a/test/unit/fixtures/layout.html b/test/unit/fixtures/layout.html index 7d123f6a0..09e7cefca 100644 --- a/test/unit/fixtures/layout.html +++ b/test/unit/fixtures/layout.html @@ -188,3 +188,24 @@ width: 25%; } + + +
      +

      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

      +
      + + + + diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 329370078..00cfa15cc 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -154,5 +154,12 @@ new Test.Unit.Runner({ var obj = layout.toObject('top'); this.assert('top' in obj, "object should have 'top' property"); + }, + + 'test dimensions on absolutely-positioned, hidden elements': function() { + var layout = $('box10').getLayout(); + + this.assertEqual(278, layout.get('width'), 'width' ); + this.assertEqual(591, layout.get('height'), 'height'); } -}); \ No newline at end of file +}); From 36a02f83234da5ec5da80acd6249c577f9452435 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 29 Aug 2010 07:17:20 -0500 Subject: [PATCH 249/502] Remove unit test for passing a DOM node into `Object.toJSON`. To continue to support this use case would require bypassing IE's native JSON implementation. (Tobie Langel, Thomas Fuchs, Samuel Lebeau, Andrew Dupont) --- CHANGELOG | 2 ++ test/unit/object_test.js | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 31368f88b..a0eea7a1f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Remove unit test for passing a DOM node into `Object.toJSON`. To continue to support this use case would require bypassing IE's native JSON implementation. (Tobie Langel, Thomas Fuchs, Samuel Lebeau, Andrew Dupont) + * Ensure hidden absolutely-positioned elements are sized correctly when they have no explicit width set. [#1084 state:resolved] (Viktor Kojouharov, Andrew Dupont) * Change custom events implementation to use the `onlosecapture` event instead of the `onfilterchange` event for non-bubbling custom events in Internet Explorer. (John-David Dalton, Andrew Dupont) diff --git a/test/unit/object_test.js b/test/unit/object_test.js index d44265dd8..7567eeeb3 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -66,9 +66,6 @@ new Test.Unit.Runner({ this.assertEqual('null', Object.toJSON(null)); var sam = new Person('sam'); this.assertEqual('"-sam"', Object.toJSON(sam)); - var element = $('test'); - element.toJSON = function(){return 'I\'m a div with id test'}; - this.assertEqual('"I\'m a div with id test"', Object.toJSON(element)); }, testObjectToHTML: function() { From 2978f36992594fe6cf573820f29b3f09345e3aee Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 8 Sep 2010 22:46:42 -0500 Subject: [PATCH 250/502] Move all offset methods to layout.js, eliminating some code repetition. --- CHANGELOG | 2 + src/dom/dom.js | 258 ---------------------------------------------- src/dom/layout.js | 107 +++++++++++-------- 3 files changed, 68 insertions(+), 299 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a0eea7a1f..f96613a82 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Move all offset methods to layout.js, eliminating some code repetition. + * Remove unit test for passing a DOM node into `Object.toJSON`. To continue to support this use case would require bypassing IE's native JSON implementation. (Tobie Langel, Thomas Fuchs, Samuel Lebeau, Andrew Dupont) * Ensure hidden absolutely-positioned elements are sized correctly when they have no explicit width set. [#1084 state:resolved] (Viktor Kojouharov, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index 798f7ac0f..68deb0c56 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2483,211 +2483,6 @@ Element.Methods = { return element; }, - /** - * Element.cumulativeOffset(@element) -> Array - * - * Returns the offsets of `element` from the top left corner of the - * document, in pixels. - * - * Returns an array in the form of `[leftValue, topValue]`. Also accessible - * as properties: `{ left: leftValue, top: topValue }`. - * - * ##### Example - * - * Assuming the div `foo` is at (25,40), then: - * - * var offset = $('foo').cumulativeOffset(); - * offset[0]; - * // -> 25 - * offset[1]; - * // -> 40 - * offset.left; - * // -> 25 - * offset.top; - * // -> 40 - **/ - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - if (element.parentNode) { - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - } - return Element._returnOffset(valueL, valueT); - }, - - /** - * Element.positionedOffset(@element) -> Array - * - * Returns `element`'s offset relative to its closest positioned ancestor - * (the element that would be returned by [[Element.getOffsetParent]]). - * - * Returns an array in the form of `[leftValue, topValue]`. Also accessible - * as properties: `{ left: leftValue, top: topValue }`. - * - * Calculates the cumulative `offsetLeft` and `offsetTop` of an element and - * all its parents _until_ it reaches an element with a position other than - * `static`. - * - * Note that all values are returned as _numbers only_ although they are - * _expressed in pixels_. - **/ - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - /** - * Element.absolutize(@element) -> Element - * - * Turns `element` into an absolutely-positioned element _without_ changing - * its position in the page layout. - **/ - absolutize: function(element) { - element = $(element); - if (Element.getStyle(element, 'position') == 'absolute') return element; - - var offsets = Element.positionedOffset(element), - top = offsets[1], - left = offsets[0], - width = element.clientWidth, - height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - /** - * Element.relativize(@element) -> Element - * - * Turns `element` into a relatively-positioned element without changing - * its position in the page layout. - * - * Used to undo a call to [[Element.absolutize]]. - **/ - relativize: function(element) { - element = $(element); - if (Element.getStyle(element, 'position') == 'relative') return element; - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), - left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - /** - * Element.cumulativeScrollOffset(@element) -> Array - * - * Calculates the cumulative scroll offset (in pixels) of an element in - * nested scrolling containers. - * - * Returns an array in the form of `[leftValue, topValue]`. Also accessible - * as properties: `{ left: leftValue, top: topValue }`. - * - * ##### Example - * - * Assuming the div `foo` is at scroll offset (0,257), then: - * - * var offset = $('foo').cumulativeOffset(); - * offset[0]; - * // -> 0 - * offset[1]; - * // -> 257 - * offset.left; - * // -> 0 - * offset.top; - * // -> 257 - **/ - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - /** - * Element.getOffsetParent(@element) -> Element - * - * Returns `element`'s closest _positioned_ ancestor. If none is found, the - * `body` element is returned. - * - * The returned element is `element`'s - * [CSS containing block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details). - **/ - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - /** - * Element.viewportOffset(@element) -> Array - * - * Returns the X/Y coordinates of element relative to the viewport. - * - * Returns an array in the form of `[leftValue, topValue]`. Also accessible - * as properties: `{ left: leftValue, top: topValue }`. - **/ - viewportOffset: function(forElement) { - var valueT = 0, - valueL = 0, - element = forElement; - - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - // Safari fix - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - /** * Element.clonePosition(@element, source[, options]) -> Element * - source (Element | String): The source element (or its ID). @@ -2882,42 +2677,6 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - // IE doesn't report offsets correctly for static elements, so we change them - // to "relative" to get the values, then change them back. - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - // IE throws an error if element is not in document - if (!element.parentNode) return $(document.body); - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - if (!element.parentNode) return Element._returnOffset(0, 0); - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - // Trigger hasLayout on the offset parent so that IE6 reports - // accurate offsetTop and offsetLeft values for position: fixed. - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -3154,23 +2913,6 @@ else if (Prototype.Browser.WebKit) { return element; }; - - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; } if ('outerHTML' in document.documentElement) { diff --git a/src/dom/layout.js b/src/dom/layout.js index 0c15382b3..535543c56 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -874,11 +874,13 @@ **/ function cumulativeOffset(element) { var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } return new Element.Offset(valueL, valueT); } @@ -889,9 +891,6 @@ * (the element that would be returned by [[Element.getOffsetParent]]). **/ function positionedOffset(element) { - // Account for the margin of the element. - var layout = element.getLayout(); - var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; @@ -903,10 +902,6 @@ if (p !== 'static') break; } } while (element); - - valueL -= layout.get('margin-top'); - valueT -= layout.get('margin-left'); - return new Element.Offset(valueL, valueT); } @@ -927,7 +922,7 @@ } /** - * Element.viewportOffset(@element) -> Array + * Element.viewportOffset(@element) -> Element.Offset * * Returns the X/Y coordinates of element relative to the viewport. **/ @@ -1015,6 +1010,62 @@ if (originalStyles) element.setStyle(originalStyles); return element; } + + if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + getOffsetParent = getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + // IE throws an error if element is not in document + if (isDetached(element)) return $(document.body); + + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + positionedOffset = positionedOffset.wrap(function(proceed, element) { + element = $(element); + if (!element.parentNode) return new Element.Offset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + hasLayout(offsetParent); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + }); + } else if (Prototype.Browser.Webkit) { + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return new Element.Offset(valueL, valueT); + }; + } + Element.addMethods({ getLayout: getLayout, @@ -1047,40 +1098,14 @@ element = $(element); if (isDetached(element)) return new Element.Offset(0, 0); - var rect = element.getBoundingClientRect(), + var rect = element.getBoundingClientRect(), docEl = document.documentElement; // The HTML element on IE < 8 has a 2px border by default, giving // an incorrect offset. We correct this by subtracting clientTop // and clientLeft. return new Element.Offset(rect.left - docEl.clientLeft, rect.top - docEl.clientTop); - }, - - positionedOffset: function(element) { - element = $(element); - var parent = element.getOffsetParent(); - if (isDetached(element)) return new Element.Offset(0, 0); - - // When the BODY is the offsetParent, IE6 mistakenly reports the - // parent as HTML. Use that as the litmus test to fix another - // annoying IE6 quirk. - if (element.offsetParent && - element.offsetParent.nodeName.toUpperCase() === 'HTML') { - return positionedOffset(element); - } - - var eOffset = element.viewportOffset(), - pOffset = isBody(parent) ? viewportOffset(parent) : - parent.viewportOffset(); - var retOffset = eOffset.relativeTo(pOffset); - - // Account for the margin of the element. - var layout = element.getLayout(); - var top = retOffset.top - layout.get('margin-top'); - var left = retOffset.left - layout.get('margin-left'); - - return new Element.Offset(left, top); } }); - } + } })(); From a4cb9bf0da6f291fd1dbbe574f0a196db7237bbd Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 8 Sep 2010 22:47:00 -0500 Subject: [PATCH 251/502] Alter an `Element#purge` test case to make IE happy. --- test/unit/dom_test.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 00f596d0d..e2c81716e 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1513,7 +1513,7 @@ new Test.Unit.Runner({ element.purge(); this.assert(!(uid in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); - this.assert(!(Object.isNumber(element._prototypeUID)), "purged element's UID should no longer exist in `Element.Storage`"); + this.assert(!(Object.isNumber(element._prototypeUID)), "purged element's UID should no longer exist as expando on element"); // Should purge elements replaced via innerHTML. var parent = new Element('div'); @@ -1521,16 +1521,18 @@ new Test.Unit.Runner({ parent.insert(child); child.store('foo', 'bar'); - child.observe('test:event', function(event) { event.stop(); }); + + var trigger = false; + child.observe('test:event', function(event) { trigger = true; }); var childUID = child._prototypeUID; parent.update(""); - + // At this point, `child` should have been purged. this.assert(!(childUID in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); - var event = child.fire('test:event'); - this.assert(!event.stopped, "fired event should not have been stopped"); + var event = child.fire('test:event'); + this.assert(!trigger, "fired event should not have triggered handler"); } }); From c05412b76203a2d8bf383822d2ae0c07f00dc1f2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 4 Oct 2010 19:29:11 -0500 Subject: [PATCH 252/502] Fix an issue in IE9 beta where the `value` attribute of an element would not get set. --- CHANGELOG | 2 ++ src/dom/dom.js | 12 ++++++++++-- test/unit/fixtures/form.html | 5 +++++ test/unit/form_test.js | 6 +++++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f96613a82..9ca93b63f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix an issue in IE9 beta where the `value` attribute of an element would not get set. (Andrew Dupont) + * Move all offset methods to layout.js, eliminating some code repetition. * Remove unit test for passing a DOM node into `Object.toJSON`. To continue to support this use case would require bypassing IE's native JSON implementation. (Tobie Langel, Thomas Fuchs, Samuel Lebeau, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index 68deb0c56..c7b0c557c 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -172,13 +172,21 @@ if (!Node.ELEMENT_NODE) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; + delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + + // Don't use the cache if we're setting the `type` attribute, as on an + // INPUT element. This prevents an issue with IE9 beta. + var node = ('type' in attributes) ? document.createElement(tagName) : + cache[tagName].cloneNode(false); + + return Element.writeAttribute(node, attributes); }; Object.extend(global.Element, element || { }); diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index c61f265ba..e8121e182 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -117,3 +117,8 @@ + +
      + + + diff --git a/test/unit/form_test.js b/test/unit/form_test.js index 22b1d0a43..ea42c619d 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -370,6 +370,7 @@ new Test.Unit.Runner({ }, testSerializeFormTroublesomeNames: function() { + var hash = { length: 'foo', bar: 'baz' }; var el = new Element('form', { action: '/' }); @@ -385,6 +386,9 @@ new Test.Unit.Runner({ }); el.appendChild(input); el.appendChild(input2); - this.assertHashEqual({ length: 'foo', bar: 'baz' }, el.serialize(true)); + this.assertHashEqual(hash, el.serialize(true)); + + var form = $('form_with_troublesome_input_names'); + this.assertHashEqual(hash, form.serialize(true)); } }); \ No newline at end of file From a7cff523c32420b9ab1560b1c1f8bcece66bd567 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 4 Oct 2010 19:37:35 -0500 Subject: [PATCH 253/502] Feature detect IE's legacy event system, since IE9 now supports DOM L2 Events. --- CHANGELOG | 2 ++ src/dom/event.js | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9ca93b63f..effe0946c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Feature detect IE's legacy event system, since IE9 now supports DOM L2 Events. (Michael Sciacqua, kangax, Andrew Dupont) + * Fix an issue in IE9 beta where the `value` attribute of an element would not get set. (Andrew Dupont) * Move all offset methods to layout.js, eliminating some code repetition. diff --git a/src/dom/event.js b/src/dom/event.js index afd2452ff..adc15656a 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -85,10 +85,11 @@ var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; + var IE_LEGACY_EVENT_SYSTEM = (window.attachEvent && !window.addEventListener); var _isButton; - if (Prototype.Browser.IE) { - // IE doesn't map left/right/middle the same way. + if (IE_LEGACY_EVENT_SYSTEM) { + // IE's event system doesn't map left/right/middle the same way. var buttonMap = { 0: 1, 1: 4, 2: 2 }; _isButton = function(event, code) { return event.button === buttonMap[code]; @@ -226,6 +227,7 @@ **/ function findElement(event, expression) { var element = Event.element(event); + if (!expression) return element; while (element) { if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { @@ -363,7 +365,7 @@ return m; }); - if (Prototype.Browser.IE) { + if (IE_LEGACY_EVENT_SYSTEM) { function _relatedTarget(event) { var element; switch (event.type) { From a7b2113bd9c855523181b731230d62c074b03a14 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 11 Oct 2010 23:02:16 -0500 Subject: [PATCH 254/502] Account for margins in `Element#positionedOffset`. --- src/dom/layout.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 535543c56..5b07cf185 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -890,7 +890,10 @@ * Returns `element`'s offset relative to its closest positioned ancestor * (the element that would be returned by [[Element.getOffsetParent]]). **/ - function positionedOffset(element) { + function positionedOffset(element) { + // Account for the margin of the element. + var layout = element.getLayout(); + var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; @@ -902,6 +905,10 @@ if (p !== 'static') break; } } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + return new Element.Offset(valueL, valueT); } From 34564c5d834b6778cdad8ed3cb4a9696a75d4871 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 11 Oct 2010 23:02:59 -0500 Subject: [PATCH 255/502] Bump version number. --- CHANGELOG | 2 ++ src/constants.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index effe0946c..86636a619 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +*1.7_rc3* (October 11, 2010) + * Feature detect IE's legacy event system, since IE9 now supports DOM L2 Events. (Michael Sciacqua, kangax, Andrew Dupont) * Fix an issue in IE9 beta where the `value` attribute of an element would not get set. (Andrew Dupont) diff --git a/src/constants.yml b/src/constants.yml index 14db9902b..97e2e43d5 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.7_rc2 +PROTOTYPE_VERSION: 1.7_rc3 From 46d273eb0185a4f49f2297081e418cb2c35329a2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 00:09:18 -0500 Subject: [PATCH 256/502] Wrap `element` in `$` for Element#cumulativeOffset, #viewportOffset, #positionedOffset, and #getOffsetParent. [#782 state:resolved] (Radoslav Stankov, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 86636a619..f92518931 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Wrap `element` in `$` for Element#cumulativeOffset, #viewportOffset, #positionedOffset, and #getOffsetParent. [#782 state:resolved] (Radoslav Stankov, Andrew Dupont) + *1.7_rc3* (October 11, 2010) * Feature detect IE's legacy event system, since IE9 now supports DOM L2 Events. (Michael Sciacqua, kangax, Andrew Dupont) diff --git a/src/dom/layout.js b/src/dom/layout.js index 5b07cf185..8b6276b82 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -849,6 +849,7 @@ * `body` element is returned. **/ function getOffsetParent(element) { + element = $(element); if (isDetached(element)) return $(document.body); // IE reports offset parent incorrectly for inline elements. @@ -873,6 +874,7 @@ * document. **/ function cumulativeOffset(element) { + element = $(element); var valueT = 0, valueL = 0; if (element.parentNode) { do { @@ -891,6 +893,8 @@ * (the element that would be returned by [[Element.getOffsetParent]]). **/ function positionedOffset(element) { + element = $(element); + // Account for the margin of the element. var layout = element.getLayout(); @@ -934,6 +938,7 @@ * Returns the X/Y coordinates of element relative to the viewport. **/ function viewportOffset(forElement) { + element = $(element); var valueT = 0, valueL = 0, docBody = document.body; var element = forElement; @@ -1059,6 +1064,7 @@ // positioned. For performance reasons, redefine Element#cumulativeOffset for // KHTML/WebKit only. cumulativeOffset = function(element) { + element = $(element); var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; From 27e6845b35c618d0e2c99b7195eb73152187fd02 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 00:09:36 -0500 Subject: [PATCH 257/502] Make the `assertNear` test helper method a bit friendlier to debug. --- test/unit/layout_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 00cfa15cc..272e4bbdf 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -123,7 +123,7 @@ new Test.Unit.Runner({ function assertNear(v1, v2, message) { var abs = Math.abs(v1 - v2); - this.assert(abs <= 1, message); + this.assert(abs <= 1, message + ' (actual: ' + v1 + ', ' + v2 + ')'); } // With percentage widths, we'll occasionally run into rounding From e4503ee9912be5abff287b88f7c27a9f43632fdd Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 00:51:15 -0500 Subject: [PATCH 258/502] Handle cases where `document` or `document.documentElement` is passed into Element#getOffsetParent. Fixes IE errors with many layout/positioning methods. [#90 state:resolved] (Padraig Kennedy, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/layout.js | 24 +++++++++++++++++++----- test/unit/dom_test.js | 4 ++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f92518931..e47770868 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Handle cases where `document` or `document.documentElement` is passed into Element#getOffsetParent. Fixes IE errors with many layout/positioning methods. [#90 state:resolved] (Padraig Kennedy, Andrew Dupont) + * Wrap `element` in `$` for Element#cumulativeOffset, #viewportOffset, #positionedOffset, and #getOffsetParent. [#782 state:resolved] (Radoslav Stankov, Andrew Dupont) *1.7_rc3* (October 11, 2010) diff --git a/src/dom/layout.js b/src/dom/layout.js index 8b6276b82..1bc880566 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -850,16 +850,19 @@ **/ function getOffsetParent(element) { element = $(element); - if (isDetached(element)) return $(document.body); + + // For unusual cases like these, we standardize on returning the BODY + // element as the offset parent. + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); // IE reports offset parent incorrectly for inline elements. var isInline = (Element.getStyle(element, 'display') === 'inline'); if (!isInline && element.offsetParent) return $(element.offsetParent); - if (element === document.body) return $(element); while ((element = element.parentNode) && element !== document.body) { if (Element.getStyle(element, 'position') !== 'static') { - return (element.nodeName === 'HTML') ? $(document.body) : $(element); + return isHtml(element) ? $(document.body) : $(element); } } @@ -1029,8 +1032,11 @@ getOffsetParent = getOffsetParent.wrap( function(proceed, element) { element = $(element); - // IE throws an error if element is not in document - if (isDetached(element)) return $(document.body); + + // For unusual cases like these, we standardize on returning the BODY + // element as the offset parent. + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); @@ -1097,6 +1103,14 @@ return element.nodeName.toUpperCase() === 'BODY'; } + function isHtml(element) { + return element.nodeName.toUpperCase() === 'HTML'; + } + + function isDocument(element) { + return element.nodeType === Node.DOCUMENT_NODE; + } + function isDetached(element) { return element !== document.body && !Element.descendantOf(element, document.body); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index e2c81716e..f5fa4691c 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1355,6 +1355,10 @@ new Test.Unit.Runner({ this.assertEqual(document.body, new Element('div').getOffsetParent(), 'body should be parent of unattached element'); + + [document, document.body, document.documentElement].each (function(node) { + this.assertEqual(document.body, Element.getOffsetParent(node)); + }, this); }, testAbsolutize: function() { From 05e0ebc2c5df6e81b1dd908a15c48b919d992f09 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 01:11:11 -0500 Subject: [PATCH 259/502] Add Object.isDate. [#443 state:resolved] (Nesterenko Dmitry, kangax, Samuel Lebeau, Andrew Dupont) --- CHANGELOG | 2 ++ src/lang/object.js | 23 +++++++++++++++++++++++ test/unit/object_test.js | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e47770868..02e2ce53e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add Object.isDate. [#443 state:resolved] (Nesterenko Dmitry, kangax, Samuel Lebeau, Andrew Dupont) + * Handle cases where `document` or `document.documentElement` is passed into Element#getOffsetParent. Fixes IE errors with many layout/positioning methods. [#90 state:resolved] (Padraig Kennedy, Andrew Dupont) * Wrap `element` in `$` for Element#cumulativeOffset, #viewportOffset, #positionedOffset, and #getOffsetParent. [#782 state:resolved] (Radoslav Stankov, Andrew Dupont) diff --git a/src/lang/object.js b/src/lang/object.js index f41c4fd7b..d00a9c4f2 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -32,6 +32,7 @@ NUMBER_CLASS = '[object Number]', STRING_CLASS = '[object String]', ARRAY_CLASS = '[object Array]', + DATE_CLASS = '[object Date]', NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && typeof JSON.stringify === 'function' && JSON.stringify(0) === '0' && @@ -513,6 +514,27 @@ function isNumber(object) { return _toString.call(object) === NUMBER_CLASS; } + + /** + * Object.isDate(object) -> Boolean + * - object (Object): The object to test. + * + * Returns `true` if `object` is of type [[Date]]; `false` otherwise. + * + * ##### Examples + * + * Object.isDate(new Date); + * //-> true + * + * Object.isDate("Dec 25, 1995"); + * //-> false + * + * Object.isDate(new Date("Dec 25, 1995")); + * //-> true + **/ + function isDate(object) { + return _toString.call(object) === DATE_CLASS; + } /** * Object.isUndefined(object) -> Boolean @@ -556,6 +578,7 @@ isFunction: isFunction, isString: isString, isNumber: isNumber, + isDate: isDate, isUndefined: isUndefined }); })(); diff --git a/test/unit/object_test.js b/test/unit/object_test.js index 7567eeeb3..b8ff0c1d2 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -160,6 +160,25 @@ new Test.Unit.Runner({ this.assert(!Object.isNumber(undefined)); this.assert(!Object.isNumber(document), 'host objects should return false rather than throw exceptions'); }, + + testObjectIsDate: function() { + var d = new Date(); + this.assert(Object.isDate(d), 'constructor with no arguments'); + this.assert(Object.isDate(new Date(0)), 'constructor with milliseconds'); + this.assert(Object.isDate(new Date(1995, 11, 17)), 'constructor with Y, M, D'); + this.assert(Object.isDate(new Date(1995, 11, 17, 3, 24, 0)), 'constructor with Y, M, D, H, M, S'); + this.assert(Object.isDate(new Date(Date.parse("Dec 25, 1995"))), 'constructor with result of Date.parse'); + + this.assert(!Object.isDate(d.valueOf()), 'Date#valueOf returns a number'); + this.assert(!Object.isDate(function() { })); + this.assert(!Object.isDate(0)); + this.assert(!Object.isDate("a string")); + this.assert(!Object.isDate([])); + this.assert(!Object.isDate({})); + this.assert(!Object.isDate(false)); + this.assert(!Object.isDate(undefined)); + this.assert(!Object.isDate(document), 'host objects should return false rather than throw exceptions'); + }, testObjectIsUndefined: function() { this.assert(Object.isUndefined(undefined)); From 8b2ed9719ad2ddd34ec541d8bd4229ab70f88e85 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 01:21:14 -0500 Subject: [PATCH 260/502] Fix issue where an Ajax request in IE sometimes returns 1223 instead of 204 as the status code. [#129 state:resolved] (adevadeh, gordyt, Andrew Dupont) --- CHANGELOG | 2 ++ src/ajax/request.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 02e2ce53e..d353ee5b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix issue where an Ajax request in IE sometimes returns 1223 instead of 204 as the status code. [#129 state:resolved] (adevadeh, gordyt, Andrew Dupont) + * Add Object.isDate. [#443 state:resolved] (Nesterenko Dmitry, kangax, Samuel Lebeau, Andrew Dupont) * Handle cases where `document` or `document.documentElement` is passed into Element#getOffsetParent. Fixes IE errors with many layout/positioning methods. [#90 state:resolved] (Padraig Kennedy, Andrew Dupont) diff --git a/src/ajax/request.js b/src/ajax/request.js index 44263a1e1..a1c5f7a1d 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -278,6 +278,8 @@ Ajax.Request = Class.create(Ajax.Base, { getStatus: function() { try { + // IE sometimes returns 1223 for a 204 response. + if (this.transport.status === 1223) return 204; return this.transport.status || 0; } catch (e) { return 0 } }, From f6e77f6595c4fe1a71239656f6b7c8ef9bd5a7fe Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 01:48:46 -0500 Subject: [PATCH 261/502] Fix issue with Event#isMiddleClick and #isRightClick on Safari and Chrome. [#731 state:resolved] (Arthur Schreiber) --- CHANGELOG | 2 ++ src/dom/event.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d353ee5b7..573386826 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix issue with Event#isMiddleClick and #isRightClick on Safari and Chrome. [#731 state:resolved] (Arthur Schreiber) + * Fix issue where an Ajax request in IE sometimes returns 1223 instead of 204 as the status code. [#129 state:resolved] (adevadeh, gordyt, Andrew Dupont) * Add Object.isDate. [#443 state:resolved] (Nesterenko Dmitry, kangax, Samuel Lebeau, Andrew Dupont) diff --git a/src/dom/event.js b/src/dom/event.js index adc15656a..39c25d6df 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -100,7 +100,8 @@ _isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; + case 1: return event.which == 2 || (event.which == 1 && event.metaKey); + case 2: return event.which == 3; default: return false; } }; From de6a58570015e10a315d3dc5751bb12d7e53f769 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 02:16:25 -0500 Subject: [PATCH 262/502] Revert Opera-specific behavior for calling Element#getStyle with (left|right|top|bottom). [#268 state:resolved] (kangax, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/dom.js | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 573386826..0dc426b36 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Revert Opera-specific behavior for calling Element#getStyle with (left|right|top|bottom). [#268 state:resolved] (kangax, Andrew Dupont) + * Fix issue with Event#isMiddleClick and #isRightClick on Safari and Chrome. [#731 state:resolved] (Arthur Schreiber) * Fix issue where an Ajax request in IE sometimes returns 1223 instead of 204 as the status code. [#129 state:resolved] (adevadeh, gordyt, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index c7b0c557c..384731496 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -2645,8 +2645,6 @@ if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': // returns '0px' for hidden elements; we want it to return null if (!Element.visible(element)) return null; From a7e51ae336f29fa61e72d557502f907f0c981409 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 15:19:56 -0500 Subject: [PATCH 263/502] Ensure Object.isFunction returns `false` for RegExp objects. [#661 state:resolved] (James, kangax, Andrew Dupont) --- CHANGELOG | 2 ++ src/lang/object.js | 3 ++- test/unit/object_test.js | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0dc426b36..297b1d473 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure Object.isFunction returns `false` for RegExp objects. [#661 state:resolved] (James, kangax, Andrew Dupont) + * Revert Opera-specific behavior for calling Element#getStyle with (left|right|top|bottom). [#268 state:resolved] (kangax, Andrew Dupont) * Fix issue with Event#isMiddleClick and #isRightClick on Safari and Chrome. [#731 state:resolved] (Arthur Schreiber) diff --git a/src/lang/object.js b/src/lang/object.js index d00a9c4f2..32ab541bf 100644 --- a/src/lang/object.js +++ b/src/lang/object.js @@ -28,6 +28,7 @@ NUMBER_TYPE = 'Number', STRING_TYPE = 'String', OBJECT_TYPE = 'Object', + FUNCTION_CLASS = '[object Function]', BOOLEAN_CLASS = '[object Boolean]', NUMBER_CLASS = '[object Number]', STRING_CLASS = '[object String]', @@ -470,7 +471,7 @@ * //-> false **/ function isFunction(object) { - return typeof object === "function"; + return _toString.call(object) === FUNCTION_CLASS; } /** diff --git a/test/unit/object_test.js b/test/unit/object_test.js index b8ff0c1d2..1d213fe04 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -125,6 +125,7 @@ new Test.Unit.Runner({ testObjectIsFunction: function() { this.assert(Object.isFunction(function() { })); this.assert(Object.isFunction(Class.create())); + this.assert(!Object.isFunction("a string")); this.assert(!Object.isFunction($("testlog"))); this.assert(!Object.isFunction([])); @@ -132,6 +133,7 @@ new Test.Unit.Runner({ this.assert(!Object.isFunction(0)); this.assert(!Object.isFunction(false)); this.assert(!Object.isFunction(undefined)); + this.assert(!Object.isFunction(/xyz/), 'regular expressions are not functions'); }, testObjectIsString: function() { From 1fcf2e029977869e3389f363607cbb1c0c36fd94 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 15:43:04 -0500 Subject: [PATCH 264/502] Extend BUTTON elements with everything defined in Form.Element.Methods. Ensure BUTTON elements are traversed in Form.getElements and serialized in Form.serialize. [#394 state:resolved] [#688 state:resolved] (Luis Gomez, Samuel Lebeau, kangax, Andrew Dupont) --- CHANGELOG | 2 + src/dom/dom.js | 3 +- src/dom/form.js | 93 ++++++++++++++++++++---------------- test/unit/fixtures/form.html | 1 + test/unit/form_test.js | 25 ++++++++-- 5 files changed, 77 insertions(+), 47 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 297b1d473..ea418e2c5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Extend BUTTON elements with everything defined in Form.Element.Methods. Ensure BUTTON elements are traversed in Form.getElements and serialized in Form.serialize. (Luis Gomez, Samuel Lebeau, kangax, Andrew Dupont) + * Ensure Object.isFunction returns `false` for RegExp objects. [#661 state:resolved] (James, kangax, Andrew Dupont) * Revert Opera-specific behavior for calling Element#getStyle with (left|right|top|bottom). [#268 state:resolved] (kangax, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index 384731496..d1352781b 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -3292,7 +3292,8 @@ Element.addMethods = function(methods) { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) + "TEXTAREA": Object.clone(Form.Element.Methods), + "BUTTON": Object.clone(Form.Element.Methods) }); } diff --git a/src/dom/form.js b/src/dom/form.js index 62d8bf072..68db8e160 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -724,68 +724,77 @@ var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ -Form.Element.Serializers = { - input: function(element, value) { +Form.Element.Serializers = (function() { + function input(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': - return Form.Element.Serializers.inputSelector(element, value); + return inputSelector(element, value); default: - return Form.Element.Serializers.textarea(element, value); + return valueSelector(element, value); } - }, - - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; - else element.checked = !!value; - }, - - textarea: function(element, value) { + } + + function inputSelector(element, value) { + if (Object.isUndefined(value)) + return element.checked ? element.value : null; + else element.checked = !!value; + } + + function valueSelector(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; - }, - - select: function(element, value) { + } + + function select(element, value) { if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } + return (element.type === 'select-one' ? selectOne : selectMany)(element); + + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; } - else opt.selected = value.include(currentValue); } + else opt.selected = value.include(currentValue); } - }, - - selectOne: function(element) { + } + + function selectOne(element) { var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, - - selectMany: function(element) { + return index >= 0 ? optionValue(element.options[index]) : null; + } + + function selectMany(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); + if (opt.selected) values.push(optionValue(opt)); } return values; - }, - - optionValue: function(opt) { - // extend element because hasAttribute may not be native - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } -}; + + function optionValue(opt) { + return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; + } + + return { + input: input, + inputSelector: inputSelector, + textarea: valueSelector, + select: select, + selectOne: selectOne, + selectMany: selectMany, + optionValue: optionValue, + button: valueSelector + }; +})(); /*--------------------------------------------------------------------------*/ diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index e8121e182..8f11487fb 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -72,6 +72,7 @@ + diff --git a/test/unit/form_test.js b/test/unit/form_test.js index ea42c619d..be72096f0 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -204,7 +204,7 @@ new Test.Unit.Runner({ testFormGetElements: function() { var elements = Form.getElements('various'), - names = $w('tf_selectOne tf_textarea tf_checkbox tf_selectMany tf_text tf_radio tf_hidden tf_password'); + names = $w('tf_selectOne tf_textarea tf_checkbox tf_selectMany tf_text tf_radio tf_hidden tf_password tf_button'); this.assertEnumEqual(names, elements.pluck('name')) }, @@ -226,7 +226,15 @@ new Test.Unit.Runner({ testFormSerialize: function() { // form is initially empty var form = $('bigform'); - var expected = { tf_selectOne:'', tf_textarea:'', tf_text:'', tf_hidden:'', tf_password:'' }; + var expected = { + tf_selectOne: '', + tf_textarea: '', + tf_text: '', + tf_hidden: '', + tf_password: '', + tf_button: '' + }; + this.assertHashEqual(expected, Form.serialize('various', true)); // set up some stuff @@ -235,10 +243,19 @@ new Test.Unit.Runner({ form['tf_text'].value = "123öäü"; form['tf_hidden'].value = "moo%hoo&test"; form['tf_password'].value = 'sekrit code'; + form['tf_button'].value = 'foo bar'; form['tf_checkbox'].checked = true; form['tf_radio'].checked = true; - var expected = { tf_selectOne:1, tf_textarea:"boo hoo!", tf_text:"123öäü", - tf_hidden:"moo%hoo&test", tf_password:'sekrit code', tf_checkbox:'on', tf_radio:'on' } + + var expected = { + tf_selectOne: 1, tf_textarea: "boo hoo!", + tf_text: "123öäü", + tf_hidden: "moo%hoo&test", + tf_password: 'sekrit code', + tf_button: 'foo bar', + tf_checkbox: 'on', + tf_radio: 'on' + }; // return params this.assertHashEqual(expected, Form.serialize('various', true)); From aeb45325d40f4a31c3c3b2687ede876b2c7e3c1c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 16:03:15 -0500 Subject: [PATCH 265/502] Fix odd behavior with `new Element('select')` in IE6-7. [#480 state:resolved] (Bruce Harris, kangax, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/dom.js | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ea418e2c5..2a6854b3c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Fix odd behavior with `new Element('select')` in IE6-7. [#480 state:resolved] (Bruce Harris, kangax, Andrew Dupont) + * Extend BUTTON elements with everything defined in Form.Element.Methods. Ensure BUTTON elements are traversed in Form.getElements and serialized in Form.serialize. (Luis Gomez, Samuel Lebeau, kangax, Andrew Dupont) * Ensure Object.isFunction returns `false` for RegExp objects. [#661 state:resolved] (James, kangax, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index d1352781b..962b39637 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -154,7 +154,18 @@ if (!Node.ELEMENT_NODE) { * var a = new Element('a', {'class': 'foo', href: '/foo.html'}).update("Next page"); **/ -(function(global) { +(function(global) { + // For performance reasons, we create new elements by cloning a "blank" + // version of a given element. But sometimes this causes problems. Skip + // the cache if: + // (a) We're creating a SELECT element (troublesome in IE6); + // (b) We're setting the `type` attribute on an INPUT element + // (troublesome in IE9). + function shouldUseCache(tagName, attributes) { + if (tagName === 'select') return false; + if ('type' in attributes) return false; + return true; + } var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ try { @@ -181,10 +192,8 @@ if (!Node.ELEMENT_NODE) { if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - // Don't use the cache if we're setting the `type` attribute, as on an - // INPUT element. This prevents an issue with IE9 beta. - var node = ('type' in attributes) ? document.createElement(tagName) : - cache[tagName].cloneNode(false); + var node = shouldUseCache(tagName, attributes) ? + cache[tagName].cloneNode(false) : document.createElement(tagName); return Element.writeAttribute(node, attributes); }; From f71bb853f70eed54fd4b4bbfeb5a8ea72874c9ff Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 16:09:12 -0500 Subject: [PATCH 266/502] Define a `relatedTarget` property on extended mouseenter/mouseleave events in IE's legacy event system. [#708 state:resolved] (Walter Smith, Tobie Langel, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/event.js | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2a6854b3c..90095513f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Define a `relatedTarget` property on extended mouseenter/mouseleave events in IE's legacy event system. [#708 state:resolved] (Walter Smith, Tobie Langel, Andrew Dupont) + * Fix odd behavior with `new Element('select')` in IE6-7. [#480 state:resolved] (Bruce Harris, kangax, Andrew Dupont) * Extend BUTTON elements with everything defined in Form.Element.Methods. Ensure BUTTON elements are traversed in Form.getElements and serialized in Form.serialize. (Luis Gomez, Samuel Lebeau, kangax, Andrew Dupont) diff --git a/src/dom/event.js b/src/dom/event.js index 39c25d6df..e1c915a76 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -370,9 +370,16 @@ function _relatedTarget(event) { var element; switch (event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; + case 'mouseover': + case 'mouseenter': + element = event.fromElement; + break; + case 'mouseout': + case 'mouseleave': + element = event.toElement; + break; + default: + return null; } return Element.extend(element); } From 6abe2a633b41044bc46a03e49291655c948278ea Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 16:27:03 -0500 Subject: [PATCH 267/502] Add a couple of test cases to guard against issues with `new Element`. --- test/unit/dom_test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index f5fa4691c..82bf714e6 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1135,6 +1135,12 @@ new Test.Unit.Runner({ elWithClassName = new Element('div', { 'class': 'firstClassName' }); this.assert(elWithClassName.hasClassName('firstClassName')); + + var radio = new Element('input', { type: 'radio', value: 'test' }); + this.assert(radio.value === 'test', 'value of a dynamically-created radio button'); + + var radio2 = new Element('input', { type: 'radio', value: 'test2' }); + this.assert(radio2.value === 'test2', 'value of a dynamically-created radio button'); }, testElementGetHeight: function() { From 18b737f935bf4b8026d1879f952afdc22c67b1d5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 17 Oct 2010 17:59:51 -0500 Subject: [PATCH 268/502] Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/form.js | 3 ++- test/unit/fixtures/form.html | 2 ++ test/unit/form_test.js | 19 +++++++++---------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 90095513f..df8c4f5bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont) + * Define a `relatedTarget` property on extended mouseenter/mouseleave events in IE's legacy event system. [#708 state:resolved] (Walter Smith, Tobie Langel, Andrew Dupont) * Fix odd behavior with `new Element('select')` in IE6-7. [#480 state:resolved] (Bruce Harris, kangax, Andrew Dupont) diff --git a/src/dom/form.js b/src/dom/form.js index 68db8e160..c0ca1b54f 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -321,7 +321,8 @@ Form.Methods = { **/ focusFirstElement: function(form) { form = $(form); - form.findFirstElement().activate(); + var element = form.findFirstElement(); + if (element) element.activate(); return form; }, diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index 8f11487fb..b0c0605d5 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -84,6 +84,8 @@ +
      +
      diff --git a/test/unit/form_test.js b/test/unit/form_test.js index be72096f0..f2c7b0a39 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -180,26 +180,25 @@ new Test.Unit.Runner({ catch(e){ return null } } - // Form.focusFirstElement shouldn't focus disabled elements var element = Form.findFirstElement('bigform'); - this.assertEqual('submit', element.id); + this.assertEqual('submit', element.id, "Form.focusFirstElement shouldn't focus disabled elements"); - // Test IE doesn't select text on buttons Form.focusFirstElement('bigform'); - if(document.selection) this.assertEqual('', getSelection(element)); + if (document.selection) this.assertEqual('', getSelection(element), "IE shouldn't select text on buttons"); - // Form.Element.activate shouldn't select text on buttons element = $('focus_text'); - this.assertEqual('', getSelection(element)); + this.assertEqual('', getSelection(element), "Form.Element.activate shouldn't select text on buttons"); - // Form.Element.activate should select text on text input elements element.activate(); - this.assertEqual('Hello', getSelection(element)); + this.assertEqual('Hello', getSelection(element), "Form.Element.activate should select text on text input elements"); - // Form.Element.activate shouldn't raise an exception when the form or field is hidden this.assertNothingRaised(function() { $('form_focus_hidden').focusFirstElement(); - }); + }, "Form.Element.activate shouldn't raise an exception when the form or field is hidden"); + + this.assertNothingRaised(function() { + $('form_empty').focusFirstElement(); + }, "Form.focusFirstElement shouldn't raise an exception when the form has no fields"); }, testFormGetElements: function() { From ccb929d0ca0d771bfe2f6a471ed26adb1809f3f8 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 18 Oct 2010 17:54:46 -0500 Subject: [PATCH 269/502] Doc fix. --- src/dom/form.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dom/form.js b/src/dom/form.js index c0ca1b54f..8088e9d3d 100644 --- a/src/dom/form.js +++ b/src/dom/form.js @@ -67,12 +67,14 @@ var Form = { * `{ hash: false }` are used. * * If you supply an `options` object, it may have the following options: - * - `hash` ([[Boolean]]): `true` to return a plain object with keys and + * + * * `hash` ([[Boolean]]): `true` to return a plain object with keys and * values (not a [[Hash]]; see below), `false` to return a String in query * string format. If you supply an `options` object with no `hash` member, * `hash` defaults to `true`. Note that this is __not__ the same as leaving * off the `options` object entirely (see above). - * - `submit` ([[Boolean]] | [[String]]): In essence: If you omit this option + * + * * `submit` ([[Boolean]] | [[String]]): In essence: If you omit this option * the first submit button in the form is included; if you supply `false`, * no submit buttons are included; if you supply the name of a submit * button, the first button with that name is included. Note that the From 704aa4033010059402336437f7fe6498ce780be0 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 18 Oct 2010 20:55:06 -0500 Subject: [PATCH 270/502] Stop appending `&_=` to the parameters for non-GET Ajax requests in Safari. We no longer support any version of Safari for which this is necessary. [#327 state:resolved] (John-David Dalton, Andrew Dupont) --- CHANGELOG | 2 ++ src/ajax/request.js | 7 ++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index df8c4f5bb..314b86d56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Stop appending `&_=` to the parameters for non-GET Ajax requests in Safari. We no longer support any version of Safari for which this is necessary. [#327 state:resolved] (John-David Dalton, Andrew Dupont) + * Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont) * Define a `relatedTarget` property on extended mouseenter/mouseleave events in IE's legacy event system. [#708 state:resolved] (Walter Smith, Tobie Langel, Andrew Dupont) diff --git a/src/ajax/request.js b/src/ajax/request.js index a1c5f7a1d..82d504f90 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -189,12 +189,9 @@ Ajax.Request = Class.create(Ajax.Base, { this.method = 'post'; } - if (params) { + if (params && this.method === 'get') { // when GET, append parameters to URL - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; + this.url += (this.url.include('?') ? '&' : '?') + params; } this.parameters = params.toQueryParams(); From 1a5f049bc42df1672a5faea9920995f8903c29e7 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 19 Oct 2010 18:50:56 -0500 Subject: [PATCH 271/502] Make `Event.extend` work with legacy IE events in IE 9. --- CHANGELOG | 2 + src/dom/event.js | 124 +++++++++++++++++++++++++++---------- test/functional/event.html | 40 +++++++++++- 3 files changed, 131 insertions(+), 35 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 314b86d56..b297040ca 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Make `Event.extend` work with legacy IE events in IE 9. (Andrew Dupont) + * Stop appending `&_=` to the parameters for non-GET Ajax requests in Safari. We no longer support any version of Safari for which this is necessary. [#327 state:resolved] (John-David Dalton, Andrew Dupont) * Ensure `Form.focusFirstElement` doesn't raise an exception on forms with no fields. [#341 state:resolved] (achernin, Andrew Dupont) diff --git a/src/dom/event.js b/src/dom/event.js index e1c915a76..dac71e6db 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -85,30 +85,71 @@ var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; - var IE_LEGACY_EVENT_SYSTEM = (window.attachEvent && !window.addEventListener); - + + + // We need to support three different event "modes": + // 1. browsers with only DOM L2 Events (WebKit, FireFox); + // 2. browsers with only IE's legacy events system (IE 6-8); + // 3. browsers with _both_ systems (IE 9 and arguably Opera). + // + // Groups 1 and 2 are easy; group three is trickier. + + var isIELegacyEvent = function(event) { return false; }; + + if (window.attachEvent) { + if (window.addEventListener) { + // Both systems are supported. We need to decide at runtime. + // (Though Opera supports both systems, the event object appears to be + // the same no matter which system is used. That means that this function + // will always return `true` in Opera, but that's OK; it keeps us from + // having to do a browser sniff. + isIELegacyEvent = function(event) { + return !(event instanceof window.Event); + }; + } else { + // No support for DOM L2 events. All events will be legacy. + isIELegacyEvent = function(event) { return true; }; + } + } + + // The two systems have different ways of indicating which button was used + // for a mouse event. var _isButton; - if (IE_LEGACY_EVENT_SYSTEM) { - // IE's event system doesn't map left/right/middle the same way. - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - _isButton = function(event, code) { - return event.button === buttonMap[code]; - }; - } else if (Prototype.Browser.WebKit) { - // In Safari we have to account for when the user holds down - // the "meta" key. - _isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 2 || (event.which == 1 && event.metaKey); - case 2: return event.which == 3; - default: return false; + + function _isButtonForDOMEvents(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + } + + var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; + function _isButtonForLegacyEvents(event, code) { + return event.button === legacyButtonMap[code]; + } + + // In WebKit we have to account for when the user holds down the "meta" key. + function _isButtonForWebKit(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 2 || (event.which == 1 && event.metaKey); + case 2: return event.which == 3; + default: return false; + } + } + + if (window.attachEvent) { + if (!window.addEventListener) { + // Legacy IE events only. + _isButton = _isButtonForLegacyEvents; + } else { + // Both systems are supported; decide at runtime. + _isButton = function(event, code) { + return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : + _isButtonForDOMEvents(event, code); } - }; + } + } else if (Prototype.Browser.WebKit) { + _isButton = _isButtonForWebKit; } else { - _isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; + _isButton = _isButtonForDOMEvents; } /** @@ -344,29 +385,31 @@ event.stopped = true; } + Event.Methods = { - isLeftClick: isLeftClick, + isLeftClick: isLeftClick, isMiddleClick: isMiddleClick, - isRightClick: isRightClick, + isRightClick: isRightClick, - element: element, + element: element, findElement: findElement, - pointer: pointer, + pointer: pointer, pointerX: pointerX, pointerY: pointerY, stop: stop }; - // Compile the list of methods that get extended onto Events. var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); - if (IE_LEGACY_EVENT_SYSTEM) { + if (window.attachEvent) { + // For IE's event system, we need to do some work to make the event + // object behave like a standard event object. function _relatedTarget(event) { var element; switch (event.type) { @@ -384,11 +427,12 @@ return Element.extend(element); } - Object.extend(methods, { + // These methods should be added _only_ to legacy IE event objects. + var additionalMethods = { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return '[object Event]' } - }); + }; /** * Event.extend(@event) -> Event @@ -405,9 +449,14 @@ // IE's method for extending events. Event.extend = function(event, element) { if (!event) return false; - if (event._extendedByPrototype) return event; + // If it's not a legacy event, it doesn't need extending. + if (!isIELegacyEvent(event)) return event; + + // Mark this event so we know not to extend a second time. + if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); // The optional `element` argument gives us a fallback value for the @@ -418,13 +467,20 @@ pageX: pointer.x, pageY: pointer.y }); - - return Object.extend(event, methods); + + Object.extend(event, methods); + Object.extend(event, additionalMethods); }; } else { + // Only DOM events, so no manual extending necessary. + Event.extend = Prototype.K; + } + + if (window.addEventListener) { + // In all browsers that support DOM L2 Events, we can augment + // `Event.prototype` directly. Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); - Event.extend = Prototype.K; } function _createResponder(element, eventName, handler) { @@ -922,7 +978,7 @@ }, handleEvent: function(event) { - var element = event.findElement(this.selector); + var element = Event.findElement(event, this.selector); if (element) this.callback.call(this.element, event, element); } }); diff --git a/test/functional/event.html b/test/functional/event.html index 9435042e9..742c62bee 100644 --- a/test/functional/event.html +++ b/test/functional/event.html @@ -120,8 +120,9 @@

      Prototype functional tests for the Event module

      $('hijack').observe('click', function(e){ el = $(this.parentNode); log(e); // this makes it fail?!? + e.preventDefault(); - + setTimeout(function() { if (window.location.hash == '#wrong') el.failed('Hijack failed (remove the fragment)') @@ -302,6 +303,7 @@

      Prototype functional tests for the Event module

    • Test 3
    + + + + + + From 5dc12bd4316f2b7f64e8fe753e14d8db867eda75 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 2 Nov 2010 01:23:39 -0500 Subject: [PATCH 272/502] Handle sparse arrays properly in `Array#_each` to match behavior with browsers' built-in `Array#forEach` (and ES5). [#790 state:resolved] (Andriy Tyurnikov, Yaffle, Andrew Dupont) --- CHANGELOG | 2 ++ src/lang/array.js | 9 +++++---- test/unit/array_test.js | 8 +++++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b297040ca..180ea4104 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Handle sparse arrays properly in `Array#_each` to match behavior with browsers' built-in `Array#forEach` (and ES5). [#790 state:resolved] (Andriy Tyurnikov, Yaffle, Andrew Dupont) + * Make `Event.extend` work with legacy IE events in IE 9. (Andrew Dupont) * Stop appending `&_=` to the parameters for non-GET Ajax requests in Safari. We no longer support any version of Safari for which this is necessary. [#327 state:resolved] (John-David Dalton, Andrew Dupont) diff --git a/src/lang/array.js b/src/lang/array.js index 7bdb9036d..85b6be91e 100644 --- a/src/lang/array.js +++ b/src/lang/array.js @@ -189,12 +189,13 @@ Array.from = $A; slice = arrayProto.slice, _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available - function each(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); + function each(iterator, context) { + for (var i = 0, length = this.length >>> 0; i < length; i++) { + if (i in this) iterator.call(context, this[i], i, this); + } } if (!_each) _each = each; - + /** * Array#clear() -> Array * diff --git a/test/unit/array_test.js b/test/unit/array_test.js index 516d52a0a..9eb3d19fd 100644 --- a/test/unit/array_test.js +++ b/test/unit/array_test.js @@ -42,7 +42,7 @@ new Test.Unit.Runner({ this.assertEnumEqual([], $A(5)); this.assertEnumEqual([], $A(true)); }, - + testClear: function(){ this.assertEnumEqual([], [].clear()); this.assertEnumEqual([], [1].clear()); @@ -185,5 +185,11 @@ new Test.Unit.Runner({ testConcat: function(){ var args = (function() { return [].concat(arguments) })(1, 2); this.assertIdentical(1, args[0][0]); + }, + + testEachOnSparseArrays: function() { + var sparseArray = [0, 1]; + sparseArray[5] = 5; + this.assertEqual('[0, 1, 5]', sparseArray.inspect(), "Array#each should skip nonexistent keys in an array"); } }); \ No newline at end of file From e65e9216ad53acfee3d8cd28373b7a395caee79e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 2 Nov 2010 01:26:07 -0500 Subject: [PATCH 273/502] Treat a 304 HTTP status as a successful response. [#331 state:resolved] (Kenneth Kin Lum, Andrew Dupont) --- CHANGELOG | 2 ++ src/ajax/request.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 180ea4104..ee5c7053e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Treat a 304 HTTP status as a successful response. [#331 state:resolved] (Kenneth Kin Lum, Andrew Dupont) + * Handle sparse arrays properly in `Array#_each` to match behavior with browsers' built-in `Array#forEach` (and ES5). [#790 state:resolved] (Andriy Tyurnikov, Yaffle, Andrew Dupont) * Make `Event.extend` work with legacy IE events in IE 9. (Andrew Dupont) diff --git a/src/ajax/request.js b/src/ajax/request.js index 82d504f90..f50ece62f 100644 --- a/src/ajax/request.js +++ b/src/ajax/request.js @@ -270,7 +270,7 @@ Ajax.Request = Class.create(Ajax.Base, { **/ success: function() { var status = this.getStatus(); - return !status || (status >= 200 && status < 300); + return !status || (status >= 200 && status < 300) || status == 304; }, getStatus: function() { From 7c26a1e7cf891d74e2d2c32b238979e44e3cee19 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 2 Nov 2010 01:38:13 -0500 Subject: [PATCH 274/502] Ensure `Element#update` works with string content that includes a LINK tag in Internet Explorer. [#264 state:resolved] (Tobias H. Michaelsen, Andrew Dupont) --- CHANGELOG | 2 ++ src/dom/dom.js | 45 +++++++++++++++++++++++++++++++++++++++---- test/unit/dom_test.js | 17 ++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ee5c7053e..09e99c83b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Ensure `Element#update` works with string content that includes a LINK tag in Internet Explorer. [#264 state:resolved] (Tobias H. Michaelsen, Andrew Dupont) + * Treat a 304 HTTP status as a successful response. [#331 state:resolved] (Kenneth Kin Lum, Andrew Dupont) * Handle sparse arrays properly in `Array#_each` to match behavior with browsers' built-in `Array#forEach` (and ES5). [#790 state:resolved] (Andriy Tyurnikov, Yaffle, Andrew Dupont) diff --git a/src/dom/dom.js b/src/dom/dom.js index 962b39637..713826f32 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -568,6 +568,21 @@ Element.Methods = { return true; } })(); + + var LINK_ELEMENT_INNERHTML_BUGGY = (function() { + try { + var el = document.createElement('div'); + el.innerHTML = ""; + var isBuggy = (el.childNodes.length === 0); + el = null; + return isBuggy; + } catch(e) { + return true; + } + })(); + + var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || + TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { var s = document.createElement("script"), @@ -582,6 +597,7 @@ Element.Methods = { s = null; return isBuggy; })(); + function update(element, content) { element = $(element); @@ -610,7 +626,7 @@ Element.Methods = { return element; } - if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (ANY_INNERHTML_BUGGY) { if (tagName in Element._insertionTranslations.tags) { while (element.firstChild) { element.removeChild(element.firstChild); @@ -619,6 +635,16 @@ Element.Methods = { .each(function(node) { element.appendChild(node) }); + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { + // IE barfs when inserting a string that beings with a LINK + // element. The workaround is to add any content to the beginning + // of the string; we'll be inserting a text node (see + // Element._getContentFromAnonymousElement below). + while (element.firstChild) { + element.removeChild(element.firstChild); + } + var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); + nodes.each(function(node) { element.appendChild(node) }); } else { element.innerHTML = content.stripScripts(); @@ -2966,11 +2992,22 @@ Element._returnOffset = function(l, t) { return result; }; -Element._getContentFromAnonymousElement = function(tagName, html) { +Element._getContentFromAnonymousElement = function(tagName, html, force) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; + + var workaround = false; + if (t) workaround = true; + else if (force) { + workaround = true; + t = ['', '', 0]; + } + + if (workaround) { + // Adding a text node to the beginning of the string (then removing it) + // fixes an issue in Internet Explorer. See Element#update above. + div.innerHTML = ' ' + t[0] + html + t[1]; + div.removeChild(div.firstChild); for (var i = t[2]; i--; ) { div = div.firstChild; } diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 82bf714e6..db4f4d12d 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -362,6 +362,23 @@ new Test.Unit.Runner({ select.update(''); this.assertEqual('option 4', select.getValue()); }, + + testElementUpdateWithLinkTag: function() { + var div = new Element('div'); + div.update(''); + this.assertEqual(1, div.childNodes.length); + var link = div.down('link'); + this.assert(link); + this.assert(link.rel === 'stylesheet'); + + div.update('

    ') + this.assertEqual(1, div.childNodes.length); + this.assertEqual(1, div.firstChild.childNodes.length); + + var link = div.down('link'); + this.assert(link); + this.assert(link.rel === 'stylesheet'); + }, testElementUpdateWithDOMNode: function() { $('testdiv').update(new Element('div').insert('bla')); From 849bcee4a334a0c601675123266b0f0da9fdfcaa Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 4 Nov 2010 23:42:36 -0500 Subject: [PATCH 275/502] Optimize `Event.stopObserving`. --- src/dom/event.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index dac71e6db..d9b477ee9 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -854,8 +854,14 @@ }); return element; } - - var responder = responders.find( function(r) { return r.handler === handler; }); + + var i = responders.length, responder; + while (i--) { + if (responders[i].handler === handler) { + responder = responders[i]; + break; + } + } if (!responder) return element; if (eventName.include(':')) { From 89b2d774ff2b460b15fb5058fb4a5298059c3e5e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 01:58:38 -0600 Subject: [PATCH 276/502] Fix a Hash test regression in IE. --- src/lang/hash.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lang/hash.js b/src/lang/hash.js index f44d9404f..57d43f996 100644 --- a/src/lang/hash.js +++ b/src/lang/hash.js @@ -333,10 +333,21 @@ var Hash = Class.create(Enumerable, (function() { function toQueryString() { return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; - + if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); + if (Object.isArray(values)) { + // We used to use `Array#map` here to get the query pair for each + // item in the array, but that caused test regressions once we + // added the sparse array behavior for array iterator methods. + // Changed to an ordinary `for` loop so that we can handle + // `undefined` values ourselves rather than have them skipped. + var queryValues = []; + for (var i = 0, len = values.length, value; i < len; i++) { + value = values[i]; + queryValues.push(toQueryPair(key, value)); + } + return results.concat(queryValues); + } } else results.push(toQueryPair(key, values)); return results; }).join('&'); From 78082ec025b629d917669caccac5f0aea849fa3b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 02:15:37 -0600 Subject: [PATCH 277/502] Ensure `Event.extend` returns the event. --- src/dom/event.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dom/event.js b/src/dom/event.js index d9b477ee9..710dcb7ca 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -470,6 +470,8 @@ Object.extend(event, methods); Object.extend(event, additionalMethods); + + return event; }; } else { // Only DOM events, so no manual extending necessary. From 87b2fda4991f2f08c68d96bc1c111aa28818aa8f Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 02:54:11 -0600 Subject: [PATCH 278/502] Make a DOM test simulate a click instead of triggering a custom event. --- test/unit/dom_test.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index db4f4d12d..1c67a5338 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -7,6 +7,18 @@ var createParagraph = function(text) { return p; } +function simulateClick(node) { + var oEvent; + if (document.createEvent) { + oEvent = document.createEvent('MouseEvents'); + oEvent.initMouseEvent('click', true, true, document.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, node); + node.dispatchEvent(oEvent); + } else { + node.click(); + } +} + new Test.Unit.Runner({ setup: function() { if (documentViewportProperties) return; @@ -1550,15 +1562,20 @@ new Test.Unit.Runner({ child.store('foo', 'bar'); var trigger = false; - child.observe('test:event', function(event) { trigger = true; }); + child.observe('click', function(event) { trigger = true; }); var childUID = child._prototypeUID; parent.update(""); // At this point, `child` should have been purged. - this.assert(!(childUID in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); - - var event = child.fire('test:event'); + this.assert( + !(childUID in Element.Storage), + "purged element's UID should no longer exist in `Element.Storage`" + ); + + // Simulate a click to be sure the element's handler has been + // unregistered. + simulateClick(child); this.assert(!trigger, "fired event should not have triggered handler"); } }); From 23a144404b189e4072d34fb40f0dcd8eeb4ec2bf Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 16:15:14 -0600 Subject: [PATCH 279/502] Fix failing layout test case in Opera. --- src/dom/layout.js | 8 ++++++++ test/unit/layout_test.js | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dom/layout.js b/src/dom/layout.js index 1bc880566..393605fa6 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -307,6 +307,14 @@ var position = element.getStyle('position'), width = element.getStyle('width'); + if (width === "0px" || width === null) { + // Opera won't report the true width of the element through + // `getComputedStyle` if it's hidden. If we got a nonsensical value, + // we need to show the element and try again. + element.style.display = 'block'; + width = element.getStyle('width'); + } + // Preserve the context in case we get a percentage value. var context = (position === 'fixed') ? document.viewport : element.parentNode; diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 272e4bbdf..a7374301b 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -39,14 +39,14 @@ new Test.Unit.Runner({ 'test layout on elements with display: none and exact width': function() { var layout = $('box2').getLayout(); - this.assert(!isDisplayed($('box3')), 'box should be hidden'); + this.assert(!isDisplayed($('box2')), 'box should be hidden'); this.assertEqual(500, layout.get('width'), 'width'); this.assertEqual( 3, layout.get('border-right'), 'border-right'); this.assertEqual( 10, layout.get('padding-bottom'), 'padding-bottom'); this.assertEqual(526, layout.get('border-box-width'), 'border-box-width'); - this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + this.assert(!isDisplayed($('box2')), 'box should still be hidden'); }, 'test layout on elements with negative margins': function() { From 14e794384e7bf89028e5e6627dcba54d2f796366 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 16:16:35 -0600 Subject: [PATCH 280/502] Add documentation for `Element.Layout` and `Event.on`. --- src/dom/event.js | 63 +++++++++++++++++++++++++++++++++++++++++++- src/dom/layout.js | 67 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 710dcb7ca..4d0ef9fe9 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -937,6 +937,7 @@ * avoids adding an observer to a number of individual elements and instead * listens on a _common ancestor_ element. * + * For more information on usage, see [[Event.on]]. **/ Event.Handler = Class.create({ /** @@ -992,7 +993,20 @@ }); /** - * Event.on(element, eventName, selector, callback) -> Event.Handler + * Event.on(element, eventName[, selector], callback) -> Event.Handler + * - element (Element | String): The DOM element to observe, or its ID. + * - eventName (String): The name of the event, in all lower case, without + * the "on" prefix — e.g., "click" (not "onclick"). + * - selector (String): A CSS selector. If specified, will call `callback` + * _only_ when it can find an element that matches `selector` somewhere + * in the ancestor chain between the event's target element and the + * given `element`. + * - callback (Function): The event handler function. Should expect two + * arguments: the event object _and_ the element that received the + * event. (If `selector` was given, this element will be the one that + * satisfies the criteria described just above; if not, it will be the + * one specified in the `element` argument). This function is **always** + * bound to `element`. * * Listens for events on an element's descendants, optionally filtering * to match a given CSS selector. @@ -1000,6 +1014,48 @@ * Creates an instance of [[Event.Handler]], calls [[Event.Handler#start]], * then returns that instance. Keep a reference to this returned instance if * you later want to unregister the observer. + * + * ##### Usage + * + * `Event.on` can be used to set up event handlers with or without event + * delegation. In its simplest form, it works just like [[Event.observe]]: + * + * $("messages").on("click", function(event) { + * // ... + * }); + * + * An optional second argument lets you specify a CSS selector for event + * delegation. This encapsulates the pattern of using [[Event#findElement]] + * to retrieve the first ancestor element matching a specific selector. + * + * $("messages").on("click", "a.comment", function(event, element) { + * // ... + * }); + * + * Note the second argument in the handler above: it references the + * element matched by the selector (in this case, an `a` tag with a class + * of `comment`). This argument is important to use because within the + * callback, the `this` keyword **will always refer to the original + * element** (in this case, the element with the id of `messages`). + * + * `Event.on` differs from `Event.observe` in one other important way: + * its return value is an instance of [[Event.Handler]]. This instance + * has a `stop` method that will remove the event handler when invoked + * (and a `start` method that will attach the event handler again after + * it's been removed). + * + * // Register the handler: + * var handler = $("messages").on("click", "a.comment", + * this.click.bind(this)); + * + * // Unregister the handler: + * handler.stop(); + * + * // Re-register the handler: + * handler.start(); + * + * This means that, unlike `Event.stopObserving`, there's no need to + * retain a reference to the handler function. **/ function on(element, eventName, selector, callback) { element = $(element); @@ -1146,6 +1202,11 @@ **/ stopObserving: stopObserving.methodize(), + /** + * Element.on(@element, eventName[, selector], callback) -> Event.Handler + * + * See [[Event.on]]. + **/ on: on.methodize(), /** diff --git a/src/dom/layout.js b/src/dom/layout.js index 393605fa6..d55fd87fd 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -224,9 +224,10 @@ **/ Element.Layout = Class.create(Hash, { /** - * new Element.Layout(element[, preCompute]) + * new Element.Layout(element[, preCompute = false]) * - element (Element): The element to be measured. - * - preCompute (Boolean): Whether to compute all values at once. + * - preCompute (Boolean): Whether to compute all values at once. Default + * is `false`. * * Declare a new layout hash. * @@ -273,6 +274,11 @@ * * Retrieve the measurement specified by `property`. Will throw an error * if the property is invalid. + * + * ##### Caveats + * + * * `Element.Layout` can measure the dimensions of an element hidden with + * CSS (`display: none`), but _only_ if its parent element is visible. **/ get: function($super, property) { // Try to fetch from the cache. @@ -386,6 +392,10 @@ * * Keys can be passed into this method as individual arguments _or_ * separated by spaces within a string. + * + * // Equivalent statements: + * someLayout.toObject('top', 'bottom', 'left', 'right'); + * someLayout.toObject('top bottom left right'); **/ toObject: function() { var args = $A(arguments); @@ -410,6 +420,10 @@ * * Keys can be passed into this method as individual arguments _or_ * separated by spaces within a string. + * + * // Equivalent statements: + * someLayout.toHash('top', 'bottom', 'left', 'right'); + * someLayout.toHash('top bottom left right'); **/ toHash: function() { var obj = this.toObject.apply(this, arguments); @@ -712,6 +726,8 @@ /** * Element.Offset#inspect() -> String + * + * Returns a debug-friendly representation of the offset. **/ inspect: function() { return "#".interpolate(this); @@ -726,6 +742,8 @@ /** * Element.Offset#toArray() -> Array + * + * Returns an array representation fo the offset in [x, y] format. **/ toArray: function() { return [this.left, this.top]; @@ -733,7 +751,10 @@ }); /** - * Element.getLayout(@element, preCompute) -> Element.Layout + * Element.getLayout(@element[, preCompute = false]) -> Element.Layout + * - element (Element): The element to be measured. + * - preCompute (Boolean): Whether to compute all values at once. Default + * is `false`. * * Returns an instance of [[Element.Layout]] for measuring an element's * dimensions. @@ -744,6 +765,35 @@ * `Element.getLayout` whenever you need a measurement. You should call * `Element.getLayout` again only when the values in an existing * `Element.Layout` object have become outdated. + * + * Remember that instances of `Element.Layout` compute values the first + * time they're asked for and remember those values for later retrieval. + * If you want to compute all an element's measurements at once, pass + * + * ##### Examples + * + * var layout = $('troz').getLayout(); + * + * layout.get('width'); //-> 150 + * layout.get('height'); //-> 500 + * layout.get('padding-left'); //-> 10 + * layout.get('margin-left'); //-> 25 + * layout.get('border-top'); //-> 5 + * layout.get('border-bottom'); //-> 5 + * + * // Won't re-compute width; remembers value from first time. + * layout.get('width'); //-> 150 + * + * // Composite values obtained by adding together other properties; + * // will re-use any values we've already looked up above. + * layout.get('padding-box-width'); //-> 170 + * layout.get('border-box-height'); //-> 510 + * + * ##### Caveats + * + * * Instances of `Element.Layout` can measure the dimensions of an + * element hidden with CSS (`display: none`), but _only_ if its parent + * element is visible. **/ function getLayout(element, preCompute) { return new Element.Layout(element, preCompute); @@ -759,6 +809,17 @@ * calling this method frequently over short spans of code, you might want * to call [[Element.getLayout]] and operate on the [[Element.Layout]] * object itself (thereby taking advantage of measurement caching). + * + * ##### Examples + * + * $('troz').measure('width'); //-> 150 + * $('troz').measure('border-top'); //-> 5 + * $('troz').measure('top'); //-> 226 + * + * ##### Caveats + * + * * `Element.measure` can measure the dimensions of an element hidden with + * CSS (`display: none`), but _only_ if its parent element is visible. **/ function measure(element, property) { return $(element).getLayout().get(property); From 33477d61fdc65fc2372d2abda7e92b5c2b3468ec Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 16:37:26 -0600 Subject: [PATCH 281/502] Update PDoc. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index 42f9e9c4d..b92409f60 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit 42f9e9c4d9e746e43503e58ffa69ce602a862a15 +Subproject commit b92409f605a65c94fcfd0df68fdd830ff9ca2195 From 0a0673a24de491452aa9ca8926adc279c14f37f6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 16:38:39 -0600 Subject: [PATCH 282/502] Update PDoc Rake task to include proper URLs for source code references. --- Rakefile | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Rakefile b/Rakefile index 62bec7ece..f3dc99ed2 100755 --- a/Rakefile +++ b/Rakefile @@ -67,6 +67,7 @@ module PrototypeHelper def self.build_doc_for(file) rm_rf(DOC_DIR) mkdir_p(DOC_DIR) + hash = current_head index_header = < @@ -76,15 +77,16 @@ module PrototypeHelper EOF PDoc.run({ :source_files => Dir[File.join('src', '**', '*.js')], - :destination => DOC_DIR, - :index_page => 'README.markdown', + :destination => DOC_DIR, + :index_page => 'README.markdown', :syntax_highlighter => syntax_highlighter, - :markdown_parser => :bluecloth, + :markdown_parser => :bluecloth, + :src_code_text => "View source on GitHub →", :src_code_href => proc { |obj| - "http://github.com/sstephenson/prototype/blob/#{hash}/#{obj.file}#LID#{obj.line_number}" + "https://github.com/sstephenson/prototype/blob/#{hash}/#{obj.file}#L#{obj.line_number}" }, :pretty_urls => false, - :bust_cache => false, + :bust_cache => false, :name => 'Prototype JavaScript Framework', :short_name => 'Prototype', :home_url => 'http://prototypejs.org', From 877e1a894400ae0c9672e8b196aab46a6e467631 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 15 Nov 2010 16:55:30 -0600 Subject: [PATCH 283/502] Fix layout test. --- test/unit/layout_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index a7374301b..7315dce6c 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -86,7 +86,7 @@ new Test.Unit.Runner({ this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box3')), 'box should still be hidden'); + this.assert(!isDisplayed($('box4')), 'box should still be hidden'); }, 'test positioning on absolutely-positioned elements': function() { From 1fb9728ed109cfd682225a13eda13acfb91a94dc Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 16 Nov 2010 01:32:16 -0600 Subject: [PATCH 284/502] Bump version number. --- CHANGELOG | 2 ++ src/constants.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 09e99c83b..702a20b1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +*1.7* (November 16, 2010) + * Ensure `Element#update` works with string content that includes a LINK tag in Internet Explorer. [#264 state:resolved] (Tobias H. Michaelsen, Andrew Dupont) * Treat a 304 HTTP status as a successful response. [#331 state:resolved] (Kenneth Kin Lum, Andrew Dupont) diff --git a/src/constants.yml b/src/constants.yml index 97e2e43d5..e3db1baf3 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.7_rc3 +PROTOTYPE_VERSION: 1.7 From 09aff1ea6dd6593f8c0f0ec942a259dad8d1bced Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 30 Nov 2010 04:55:15 -0600 Subject: [PATCH 285/502] First pass on MASSIVE rewrite of dom.js. Some methods are missing, due to be moved to layout.js; therefore some tests fail. --- src/dom/dom.js | 3688 +++++++++++++++++++---------------------- test/unit/dom_test.js | 16 +- 2 files changed, 1707 insertions(+), 1997 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 713826f32..3e7f71825 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1,172 +1,122 @@ -/** section: DOM, related to: Element - * $(id) -> Element - * $(id...) -> [Element...] - * - id (String | Element): A DOM node or a string that references a node's - * ID. - * - * If provided with a string, returns the element in the document with - * matching ID; otherwise returns the passed element. - * - * Takes in an arbitrary number of arguments. Returns one [[Element]] if - * given one argument; otherwise returns an [[Array]] of [[Element]]s. - * - * All elements returned by the function are "extended" with [[Element]] - * instance methods. - * - * ##### More Information - * - * The [[$]] function is the cornerstone of Prototype. Not only does it - * provide a handy alias for `document.getElementById`, it also lets you pass - * indifferently IDs (strings) or DOM node references to your functions: - * - * function foo(element) { - * element = $(element); - * // rest of the function... - * } - * - * Code written this way is flexible — you can pass it the ID of the element - * or the element itself without any type sniffing. - * - * Invoking it with only one argument returns the [[Element]], while invoking it - * with multiple arguments returns an [[Array]] of [[Element]]s (and this - * works recursively: if you're twisted, you could pass it an array - * containing some arrays, and so forth). As this is dependent on - * `getElementById`, [W3C specs](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-getElBId) - * apply: nonexistent IDs will yield `null` and IDs present multiple times in - * the DOM will yield erratic results. *If you're assigning the same ID to - * multiple elements, you're doing it wrong!* - * - * The function also *extends every returned element* with [[Element.extend]] - * so you can use Prototype's DOM extensions on it. In the following code, - * the two lines are equivalent. However, the second one feels significantly - * more object-oriented: - * - * // Note quite OOP-like... - * Element.hide('itemId'); - * // A cleaner feel, thanks to guaranted extension - * $('itemId').hide(); - * - * However, when using iterators, leveraging the [[$]] function makes for - * more elegant, more concise, and also more efficient code: - * - * ['item1', 'item2', 'item3'].each(Element.hide); - * // The better way: - * $('item1', 'item2', 'item3').invoke('hide'); - * - * See [How Prototype extends the DOM](http://prototypejs.org/learn/extensions) - * for more info. -**/ - -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - -/** section: DOM - * class Element - * - * The [[Element]] object provides a variety of powerful DOM methods for - * interacting with DOM elements — creating them, updating them, - * traversing them, etc. You can access these either as methods of [[Element]] - * itself, passing in the element to work with as the first argument, or as - * methods on extended element *instances*: - * - * // Using Element: - * Element.addClassName('target', 'highlighted'); - * - * // Using an extended element instance: - * $('target').addClassName('highlighted'); - * - * [[Element]] is also a constructor for building element instances from scratch, - * see [`new Element`](#new-constructor) for details. - * - * Most [[Element]] methods return the element instance, so that you can chain - * them easily: - * - * $('message').addClassName('read').update('I read this message!'); - * - * ##### More Information - * - * For more information about extended elements, check out ["How Prototype - * extends the DOM"](http://prototypejs.org/learn/extensions), which will walk - * you through the inner workings of Prototype's DOM extension mechanism. -**/ - -/** - * new Element(tagName[, attributes]) - * - tagName (String): The name of the HTML element to create. - * - attributes (Object): An optional group of attribute/value pairs to set on - * the element. - * - * Creates an HTML element with `tagName` as the tag name, optionally with the - * given attributes. This can be markedly more concise than working directly - * with the DOM methods, and takes advantage of Prototype's workarounds for - * various browser issues with certain attributes: - * - * ##### Example - * - * // The old way: - * var a = document.createElement('a'); - * a.setAttribute('class', 'foo'); - * a.setAttribute('href', '/foo.html'); - * a.appendChild(document.createTextNode("Next page")); - * - * // The new way: - * var a = new Element('a', {'class': 'foo', href: '/foo.html'}).update("Next page"); -**/ - -(function(global) { +(function(GLOBAL) { + + var UNDEFINED = void 0; + var SLICE = Array.prototype.slice; + + // Try to reuse the same created element as much as possible. We'll use + // this DIV for capability checks (where possible) and for normalizing + // HTML content. + var DIV = document.createElement('div'); + + /** section: DOM, related to: Element + * $(id) -> Element + * $(id...) -> [Element...] + * - id (String | Element): A DOM node or a string that references a node's + * ID. + * + * If provided with a string, returns the element in the document with + * matching ID; otherwise returns the passed element. + * + * Takes in an arbitrary number of arguments. Returns one [[Element]] if + * given one argument; otherwise returns an [[Array]] of [[Element]]s. + * + * All elements returned by the function are "extended" with [[Element]] + * instance methods. + * + * ##### More Information + * + * The [[$]] function is the cornerstone of Prototype. Not only does it + * provide a handy alias for `document.getElementById`, it also lets you pass + * indifferently IDs (strings) or DOM node references to your functions: + * + * function foo(element) { + * element = $(element); + * // rest of the function... + * } + * + * Code written this way is flexible — you can pass it the ID of the element + * or the element itself without any type sniffing. + * + * Invoking it with only one argument returns the [[Element]], while invoking it + * with multiple arguments returns an [[Array]] of [[Element]]s (and this + * works recursively: if you're twisted, you could pass it an array + * containing some arrays, and so forth). As this is dependent on + * `getElementById`, [W3C specs](http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-getElBId) + * apply: nonexistent IDs will yield `null` and IDs present multiple times in + * the DOM will yield erratic results. *If you're assigning the same ID to + * multiple elements, you're doing it wrong!* + * + * The function also *extends every returned element* with [[Element.extend]] + * so you can use Prototype's DOM extensions on it. In the following code, + * the two lines are equivalent. However, the second one feels significantly + * more object-oriented: + * + * // Note quite OOP-like... + * Element.hide('itemId'); + * // A cleaner feel, thanks to guaranted extension + * $('itemId').hide(); + * + * However, when using iterators, leveraging the [[$]] function makes for + * more elegant, more concise, and also more efficient code: + * + * ['item1', 'item2', 'item3'].each(Element.hide); + * // The better way: + * $('item1', 'item2', 'item3').invoke('hide'); + * + * See [How Prototype extends the DOM](http://prototypejs.org/learn/extensions) + * for more info. + **/ + function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); + } + + GLOBAL.$ = $; + + + // Define the DOM Level 2 node type constants if they're missing. + if (!Node) GLOBAL.Node = {}; + + if (!GLOBAL.Node.ELEMENT_NODE) { + Object.extend(GLOBAL.Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); + } + + // The cache for all our created elements. + var ELEMENT_CACHE = {}; + // For performance reasons, we create new elements by cloning a "blank" // version of a given element. But sometimes this causes problems. Skip // the cache if: // (a) We're creating a SELECT element (troublesome in IE6); // (b) We're setting the `type` attribute on an INPUT element // (troublesome in IE9). - function shouldUseCache(tagName, attributes) { + function shouldUseCreationCache(tagName, attributes) { if (tagName === 'select') return false; if ('type' in attributes) return false; return true; } + // IE requires that `name` and `type` attributes be set this way. var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ try { var el = document.createElement(''); @@ -176,67 +126,124 @@ if (!Node.ELEMENT_NODE) { return false; } })(); - - var element = global.Element; - - global.Element = function(tagName, attributes) { - attributes = attributes || { }; + + + /** + * new Element(tagName[, attributes]) + * - tagName (String): The name of the HTML element to create. + * - attributes (Object): An optional group of attribute/value pairs to set on + * the element. + * + * Creates an HTML element with `tagName` as the tag name, optionally with the + * given attributes. This can be markedly more concise than working directly + * with the DOM methods, and takes advantage of Prototype's workarounds for + * various browser issues with certain attributes: + * + * ##### Example + * + * // The old way: + * var a = document.createElement('a'); + * a.setAttribute('class', 'foo'); + * a.setAttribute('href', '/foo.html'); + * a.appendChild(document.createTextNode("Next page")); + * + * // The new way: + * var a = new Element('a', { 'class': 'foo', href: '/foo.html' }).update("Next page"); + **/ + var oldElement = GLOBAL.Element; + function Element(tagName, attributes) { + attributes = attributes || {}; tagName = tagName.toLowerCase(); - var cache = Element.cache; if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; + delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - - var node = shouldUseCache(tagName, attributes) ? - cache[tagName].cloneNode(false) : document.createElement(tagName); + if (!ELEMENT_CACHE[tagName]) + ELEMENT_CACHE[tagName] = Element.extend(document.createElement(tagName)); + var node = shouldUseCreationCache(tagName, attributes) ? + ELEMENT_CACHE[tagName].cloneNode(false) : document.createElement(tagName); + return Element.writeAttribute(node, attributes); - }; + } - Object.extend(global.Element, element || { }); - if (element) global.Element.prototype = element.prototype; + GLOBAL.Element = Element; -})(this); - -Element.idCounter = 1; -Element.cache = { }; - -// Performs cleanup on an element before it is removed from the page. -// See `Element#purge`. -Element._purgeElement = function(element) { - var uid = element._prototypeUID; - if (uid) { - // Must go first because it relies on Element.Storage. - Element.stopObserving(element); - element._prototypeUID = void 0; - delete Element.Storage[uid]; - } -} + Object.extend(GLOBAL.Element, oldElement || {}); + if (oldElement) GLOBAL.Element.prototype = oldElement.prototype; + + /** + * mixin Element.Methods + * + * [[Element.Methods]] is a mixin for DOM elements. The methods of this object + * are accessed through the [[$]] utility or through the [[Element]] object and + * shouldn't be accessed directly. + * + * ##### Examples + * + * Hide the element + * + * $(element).hide(); + * + * Return an [[Enumerable]] of all descendant nodes of the element with the id + * "article" + * + * $('articles').descendants(); + **/ + Element.Methods = { ByTag: {}, Simulated: {} }; -/** - * mixin Element.Methods - * - * [[Element.Methods]] is a mixin for DOM elements. The methods of this object - * are accessed through the [[$]] utility or through the [[Element]] object and - * shouldn't be accessed directly. - * - * ##### Examples - * - * Hide the element - * - * $(element).hide(); - * - * Return an [[Enumerable]] of all descendant nodes of the element with the id - * "article" - * - * $('articles').descendants(); -**/ -Element.Methods = { + // Temporary object for holding all our initial element methods. We'll add + // them all at once at the bottom of this file. + var methods = {}; + + /** + * Element.inspect(@element) -> String + * + * Returns the debug-oriented string representation of `element`. + * + * For more information on `inspect` methods, see [[Object.inspect]]. + * + * language: html + *
      + *
    • Golden Delicious
    • + *
    • Mutsu
    • + *
    • McIntosh
    • + *
    • + *
    + * + * And the associated JavaScript: + * + * $('golden-delicious').inspect(); + * // -> '
  • ' + * + * $('mutsu').inspect(); + * // -> '
  • ' + * + * $('mutsu').next().inspect(); + * // -> '
  • ' + **/ + var INSPECT_ATTRIBUTES = { id: 'id', className: 'class' }; + function inspect(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + + var attribute; + for (var property in INSPECT_ATTRIBUTES) { + attribute = INSPECT_ATTRIBUTES[property]; + value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + } + + return result + '>'; + } + + methods.inspect = inspect; + + // VISIBLITY + /** * Element.visible(@element) -> Boolean * @@ -278,10 +285,10 @@ Element.Methods = { * $('hidden-by-css').visible(); * // -> true **/ - visible: function(element) { - return $(element).style.display != 'none'; - }, - + function visible(element) { + return $(element).style.display !== 'none'; + } + /** * Element.toggle(@element) -> Element * @@ -327,11 +334,14 @@ Element.Methods = { * $('hidden-by-css').toggle(); // WONT' WORK! * // -> Element (div#hidden-by-css is still hidden!) **/ - toggle: function(element) { + function toggle(element, bool) { element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); + if (Object.isUndefined(bool)) + bool = !Element.visible(element); + Element[bool ? 'show' : 'hide'](element); + return element; - }, + } /** * Element.hide(@element) -> Element @@ -356,13 +366,13 @@ Element.Methods = { * * $('content', 'navigation', 'footer').invoke('hide'); * // -> [Element, Element, Element] - **/ - hide: function(element) { + **/ + function hide(element) { element = $(element); element.style.display = 'none'; return element; - }, - + } + /** * Element.show(@element) -> Element * @@ -406,12 +416,22 @@ Element.Methods = { * $('hidden-by-css').show(); // DOES NOT WORK! * // -> Element (div#error-message is still hidden!) **/ - show: function(element) { + function show(element) { element = $(element); element.style.display = ''; return element; - }, - + } + + + Object.extend(methods, { + visible: visible, + toggle: toggle, + hide: hide, + show: show + }); + + // MANIPULATION + /** * Element.remove(@element) -> Element * @@ -445,12 +465,68 @@ Element.Methods = { *
  • Ida Red
  • * **/ - remove: function(element) { + function remove(element) { element = $(element); element.parentNode.removeChild(element); return element; - }, + } + + // see: http://support.microsoft.com/kb/276228 + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + // see: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "
    "; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var LINK_ELEMENT_INNERHTML_BUGGY = (function() { + try { + var el = document.createElement('div'); + el.innerHTML = ""; + var isBuggy = (el.childNodes.length === 0); + el = null; + return isBuggy; + } catch(e) { + return true; + } + })(); + + var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || + TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + /** * Element.update(@element[, newContent]) -> Element * @@ -540,127 +616,65 @@ Element.Methods = { * $('fruits').innerHTML; * // -> 'I am a fruit and my name is "apple".' **/ - update: (function(){ - - // see: http://support.microsoft.com/kb/276228 - var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ - var el = document.createElement("select"), - isBuggy = true; - el.innerHTML = ""; - if (el.options && el.options[0]) { - isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; - } - el = null; - return isBuggy; - })(); - - // see: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx - var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ - try { - var el = document.createElement("table"); - if (el && el.tBodies) { - el.innerHTML = ""; - var isBuggy = typeof el.tBodies[0] == "undefined"; - el = null; - return isBuggy; - } - } catch (e) { - return true; - } - })(); - - var LINK_ELEMENT_INNERHTML_BUGGY = (function() { - try { - var el = document.createElement('div'); - el.innerHTML = ""; - var isBuggy = (el.childNodes.length === 0); - el = null; - return isBuggy; - } catch(e) { - return true; - } - })(); + function update(element, content) { + element = $(element); - var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || - TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; - - var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { - var s = document.createElement("script"), - isBuggy = false; - try { - s.appendChild(document.createTextNode("")); - isBuggy = !s.firstChild || - s.firstChild && s.firstChild.nodeType !== 3; - } catch (e) { - isBuggy = true; - } - s = null; - return isBuggy; - })(); + // Purge the element's existing contents of all storage keys and + // event listeners, since said content will be replaced no matter + // what. + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); - - function update(element, content) { - element = $(element); - var purgeElement = Element._purgeElement; + if (content && content.toElement) + content = content.toElement(); - // Purge the element's existing contents of all storage keys and - // event listeners, since said content will be replaced no matter - // what. - var descendants = element.getElementsByTagName('*'), - i = descendants.length; - while (i--) purgeElement(descendants[i]); + if (Object.isElement(content)) + return element.update().insert(content); - if (content && content.toElement) - content = content.toElement(); - - if (Object.isElement(content)) - return element.update().insert(content); - - content = Object.toHTML(content); - - var tagName = element.tagName.toUpperCase(); - - if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { - // scripts are not evaluated when updating SCRIPT element - element.text = content; - return element; - } - - if (ANY_INNERHTML_BUGGY) { - if (tagName in Element._insertionTranslations.tags) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { - element.appendChild(node) - }); - } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { - // IE barfs when inserting a string that beings with a LINK - // element. The workaround is to add any content to the beginning - // of the string; we'll be inserting a text node (see - // Element._getContentFromAnonymousElement below). - while (element.firstChild) { - element.removeChild(element.firstChild); - } - var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); - nodes.each(function(node) { element.appendChild(node) }); - } - else { - element.innerHTML = content.stripScripts(); - } - } - else { + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + // Scripts are not evaluated when updating a SCRIPT element. + element.text = content; + return element; + } + + if (ANY_INNERHTML_BUGGY) { + if (tagName in INSERTION_TRANSLATIONS.tags) { + while (element.firstChild) + element.removeChild(element.firstChild); + + var nodes = getContentFromAnonymousElement(tagName, content.stripScripts()); + for (var i = 0, node; node = nodes[i]; i++) + element.appendChild(node); + + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { + // IE barfs when inserting a string that beings with a LINK + // element. The workaround is to add any content to the beginning + // of the string; we'll be inserting a text node (see + // getContentFromAnonymousElement below). + while (element.firstChild) + element.removeChild(element.firstChild); + + var nodes = getContentFromAnonymousElement(tagName, + content.stripScripts(), true); + + for (var i = 0, node; node = nodes[i]; i++) + element.appendChild(node); + } else { element.innerHTML = content.stripScripts(); } - - content.evalScripts.bind(content).defer(); - return element; + } else { + element.innerHTML = content.stripScripts(); } - - return update; - })(), - + + content.evalScripts.bind(content).defer(); + return element; + } + /** * Element.replace(@element[, newContent]) -> Element * @@ -731,19 +745,128 @@ Element.Methods = { * (`Element.replace('foo', '

    Bar

    ')`). * **/ - replace: function(element, content) { + function replace(element, content) { element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { + + if (content && content.toElement) { + content = content.toElement(); + } else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } + element.parentNode.replaceChild(content, element); return element; - }, + } + + var INSERTION_TRANSLATIONS = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + + tags: { + TABLE: ['
    No record clicked
    test
    test
    ', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } + }; + + var tags = INSERTION_TRANSLATIONS.tags; + + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD + }); + + function replace_IE(element, content) { + element = $(element); + if (content && content.toElement) + content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (tagName in INSERTION_TRANSLATIONS.tags) { + var nextSibling = Element.next(element); + var fragments = getContentFromAnonymousElement( + tagName, content.stripScripts()); + + parent.removeChild(element); + + var iterator; + if (nextSibling) + iterator = function(node) { parent.insertBefore(node, nextSibling) }; + else + iterator = function(node) { parent.appendChild(node); } + + fragments.each(iterator); + } else { + // We don't need to special-case this one. + element.outerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + if ('outerHTML' in document.documentElement) + replace = replace_IE; + + function isContent(content) { + if (Object.isUndefined(content) || content === null) return false; + + if (Object.isString(content) || Object.isNumber(content)) return true; + if (Object.isElement(content)) return true; + if (content.toElement || content.toHTML) return true; + + return false; + } + + // This private method does the bulk of the work for Element#insert. The + // actual insert method handles argument normalization and multiple + // content insertions. + function insertContentAt(element, content, position) { + position = position.toLowerCase(); + var method = INSERTION_TRANSLATIONS[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + method(element, content); + return element; + } + + content = Object.toHTML(content); + var tagName = ((position === 'before' || position === 'after') ? + element.parentNode : element).tagName.toUpperCase(); + + var childNodes = getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position === 'top' || position === 'after') childNodes.reverse(); + + for (var i = 0, node; node = childNodes[i]; i++) + method(element, node); + + content.evalScripts.bind(content).defer(); + } /** * Element.insert(@element, content) -> Element @@ -790,42 +913,18 @@ Element.Methods = { * after: "
    " * }); **/ - insert: function(element, insertions) { + function insert(element, insertions) { element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - + + if (isContent(insertions)) + insertions = { bottom: insertions }; + + for (var position in insertions) + insertContentAt(element, insertions[position], position); + + return element; + } + /** * Element.wrap(@element, wrapper[, attributes]) -> Element * - wrapper (Element | String): An element to wrap `element` inside, or @@ -894,56 +993,199 @@ Element.Methods = { * HTML. As a workaround, use the generic version instead * (`Element.wrap('foo', 'p')`). **/ - wrap: function(element, wrapper, attributes) { + function wrap(element, wrapper, attributes) { element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); + + if (Object.isElement(wrapper)) { + // The wrapper argument is a DOM node. + $(wrapper).writeAttribute(attributes || {}); + } else if (Object.isString(wrapper)) { + // The wrapper argument is a string representing a tag name. + wrapper = new Element(wrapper, attributes); + } else { + // No wrapper was specified, which means the second argument is a set + // of attributes. + wrapper = new Element('div', wrapper); + } + if (element.parentNode) element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; - }, - + } + /** - * Element.inspect(@element) -> String + * Element.cleanWhitespace(@element) -> Element * - * Returns the debug-oriented string representation of `element`. + * Removes all of `element`'s child text nodes that contain *only* + * whitespace. Returns `element`. + * + * This can be very useful when using standard properties like `nextSibling`, + * `previousSibling`, `firstChild` or `lastChild` to walk the DOM. Usually + * you'd only do that if you are interested in all of the DOM nodes, not + * just Elements (since if you just need to traverse the Elements in the + * DOM tree, you can use [[Element.up]], [[Element.down]], + * [[Element.next]], and [[Element.previous]] instead). + * + * #### Example + * + * Consider the following HTML snippet: * - * For more information on `inspect` methods, see [[Object.inspect]]. - * * language: html - *
  • EOF PDoc.run({ - :source_files => Dir[File.join('src', '**', '*.js')], + :source_files => Dir[File.join('src', 'prototype', '**', '*.js')], :destination => DOC_DIR, :index_page => 'README.markdown', :syntax_highlighter => syntax_highlighter, diff --git a/src/prototype.js b/src/prototype.js index 3be377568..f493a7475 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -1,188 +1,6 @@ -/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %> - * (c) 2005-2010 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -/** - * Prototype - * - * The [[Prototype]] namespace provides fundamental information about the - * Prototype library you're using, as well as a central repository for default - * iterators or functions. - * - * We say "namespace," because the [[Prototype]] object is not intended for - * instantiation, nor for mixing in other objects. It's really just... a - * namespace. - * - * ##### Your version of Prototype - * - * Your scripts can check against a particular version of Prototype by - * examining [[Prototype.Version]], which is a version [[String]] (e.g. - * "<%= PROTOTYPE_VERSION %>"). The famous - * [script.aculo.us](http://script.aculo.us) library does this at load time to - * ensure it's being used with a reasonably recent version of Prototype, for - * instance. - * - * ##### Browser features - * - * Prototype also provides a (nascent) repository of - * [[Prototype.BrowserFeatures browser feature information]], which it then - * uses here and there in its source code. The idea is, first, to make - * Prototype's source code more readable; and second, to centralize whatever - * scripting trickery might be necessary to detect the browser feature, in - * order to ease maintenance. - * - * ##### Default iterators and functions - * - * Numerous methods in Prototype objects (most notably the [[Enumerable]] - * module) let the user pass in a custom iterator, but make it optional by - * defaulting to an "identity function" (an iterator that just returns its - * argument, untouched). This is the [[Prototype.K]] function, which you'll - * see referred to in many places. - * - * Many methods also take it easy by protecting themselves against missing - * methods here and there, reverting to empty functions when a supposedly - * available method is missing. Such a function simply ignores its potential - * arguments, and does nothing whatsoever (which is, oddly enough, - * blazing fast). The quintessential empty function sits, unsurprisingly, - * at [[Prototype.emptyFunction]] (note the lowercase first letter). -**/ -var Prototype = { - - /** - * Prototype.Version -> String - * - * The version of the Prototype library you are using (e.g. - * "<%= PROTOTYPE_VERSION %>"). - **/ - Version: '<%= PROTOTYPE_VERSION %>', - - /** - * Prototype.Browser - * - * A collection of [[Boolean]] values indicating the browser which is - * currently in use. Available properties are `IE`, `Opera`, `WebKit`, - * `MobileSafari` and `Gecko`. - * - * Example - * - * Prototype.Browser.WebKit; - * //-> true, when executed in any WebKit-based browser. - **/ - Browser: (function(){ - var ua = navigator.userAgent; - // Opera (at least) 8.x+ has "Opera" as a [[Class]] of `window.opera` - // This is a safer inference than plain boolean type conversion of `window.opera` - var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; - return { - IE: !!window.attachEvent && !isOpera, - Opera: isOpera, - WebKit: ua.indexOf('AppleWebKit/') > -1, - Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile/.test(ua) - } - })(), - - /** - * Prototype.BrowserFeatures - * - * A collection of [[Boolean]] values indicating the presence of specific - * browser features. - **/ - BrowserFeatures: { - /** - * Prototype.BrowserFeatures.XPath -> Boolean - * - * Used internally to detect if the browser supports - * [DOM Level 3 XPath](http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html). - **/ - XPath: !!document.evaluate, - - /** - * Prototype.BrowserFeatures.SelectorsAPI -> Boolean - * - * Used internally to detect if the browser supports the - * [NodeSelector API](http://www.w3.org/TR/selectors-api/#nodeselector). - **/ - SelectorsAPI: !!document.querySelector, - - /** - * Prototype.BrowserFeatures.ElementExtensions -> Boolean - * - * Used internally to detect if the browser supports extending html element - * prototypes. - **/ - ElementExtensions: (function() { - var constructor = window.Element || window.HTMLElement; - return !!(constructor && constructor.prototype); - })(), - SpecificElementExtensions: (function() { - // First, try the named class - if (typeof window.HTMLDivElement !== 'undefined') - return true; - - var div = document.createElement('div'), - form = document.createElement('form'), - isSupported = false; - - if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { - isSupported = true; - } - - div = form = null; - - return isSupported; - })() - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - /** - * Prototype.emptyFunction([argument...]) -> undefined - * - argument (Object): Optional arguments - * - * The [[Prototype.emptyFunction]] does nothing... and returns nothing! - * - * It is used thoughout the framework to provide a fallback function in order - * to cut down on conditionals. Typically you'll find it as a default value - * for optional callback functions. - **/ - emptyFunction: function() { }, - - /** - * Prototype.K(argument) -> argument - * - argument (Object): Optional argument... - * - * [[Prototype.K]] is Prototype's very own - * [identity function](http://en.wikipedia.org/wiki/Identity_function), i.e. - * it returns its `argument` untouched. - * - * This is used throughout the framework, most notably in the [[Enumerable]] - * module as a default value for iterators. - * - * ##### Examples - * - * Prototype.K('hello world!'); - * // -> 'hello world!' - * - * Prototype.K(200); - * // -> 200 - * - * Prototype.K(Prototype.K); - * // -> Prototype.K - **/ - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - -//= require "lang" -//= require "ajax" -//= require "dom" - -//= require "deprecated" +//= compat +//= require "./prototype/prototype" +//= require "./prototype/lang" +//= require "./prototype/ajax" +//= require "./prototype/dom" +//= require "./prototype/deprecated" diff --git a/src/ajax.js b/src/prototype/ajax.js similarity index 99% rename from src/ajax.js rename to src/prototype/ajax.js index b246aa854..137be07dd 100644 --- a/src/ajax.js +++ b/src/prototype/ajax.js @@ -1,3 +1,12 @@ +//= compat +//= require "ajax/ajax" +//= require "ajax/responders" +//= require "ajax/base" +//= require "ajax/request" +//= require "ajax/response" +//= require "ajax/updater" +//= require "ajax/periodical_updater" + /** * == Ajax == * @@ -31,7 +40,7 @@ * * * `asynchronous` ([[Boolean]]; default `true`): Determines whether * `XMLHttpRequest` is used asynchronously or not. Synchronous usage is - * **strongly discouraged** — it halts all script execution for the + * **strongly discouraged** — it halts all script execution for the * duration of the request _and_ blocks the browser UI. * * `contentType` ([[String]]; default `application/x-www-form-urlencoded`): * The `Content-type` header for your request. Change this header if you @@ -106,11 +115,3 @@ * happened during the request. * **/ - -//= require "ajax/ajax" -//= require "ajax/responders" -//= require "ajax/base" -//= require "ajax/request" -//= require "ajax/response" -//= require "ajax/updater" -//= require "ajax/periodical_updater" diff --git a/src/ajax/ajax.js b/src/prototype/ajax/ajax.js similarity index 100% rename from src/ajax/ajax.js rename to src/prototype/ajax/ajax.js diff --git a/src/ajax/base.js b/src/prototype/ajax/base.js similarity index 100% rename from src/ajax/base.js rename to src/prototype/ajax/base.js diff --git a/src/ajax/periodical_updater.js b/src/prototype/ajax/periodical_updater.js similarity index 100% rename from src/ajax/periodical_updater.js rename to src/prototype/ajax/periodical_updater.js diff --git a/src/ajax/request.js b/src/prototype/ajax/request.js similarity index 100% rename from src/ajax/request.js rename to src/prototype/ajax/request.js diff --git a/src/ajax/responders.js b/src/prototype/ajax/responders.js similarity index 100% rename from src/ajax/responders.js rename to src/prototype/ajax/responders.js diff --git a/src/ajax/response.js b/src/prototype/ajax/response.js similarity index 100% rename from src/ajax/response.js rename to src/prototype/ajax/response.js diff --git a/src/ajax/updater.js b/src/prototype/ajax/updater.js similarity index 100% rename from src/ajax/updater.js rename to src/prototype/ajax/updater.js diff --git a/src/deprecated.js b/src/prototype/deprecated.js similarity index 100% rename from src/deprecated.js rename to src/prototype/deprecated.js diff --git a/src/dom.js b/src/prototype/dom.js similarity index 98% rename from src/dom.js rename to src/prototype/dom.js index 79d6924d1..6003cd048 100644 --- a/src/dom.js +++ b/src/prototype/dom.js @@ -1,3 +1,11 @@ +//= compat +//= require "dom/dom" +//= require "dom/layout" +//= require "dom/selector" +//= require +//= require "dom/form" +//= require "dom/event" + /** * == DOM == * Extensions to DOM elements, plus other utilities for DOM traversal @@ -19,11 +27,4 @@ * **/ -//= require "dom/dom" -//= require "dom/layout" -//= require "dom/selector" -//= require -//= require "dom/form" -//= require "dom/event" - Element.addMethods(); diff --git a/src/dom/dom.js b/src/prototype/dom/dom.js similarity index 100% rename from src/dom/dom.js rename to src/prototype/dom/dom.js diff --git a/src/dom/event.js b/src/prototype/dom/event.js similarity index 100% rename from src/dom/event.js rename to src/prototype/dom/event.js diff --git a/src/dom/form.js b/src/prototype/dom/form.js similarity index 100% rename from src/dom/form.js rename to src/prototype/dom/form.js diff --git a/src/dom/layout.js b/src/prototype/dom/layout.js similarity index 100% rename from src/dom/layout.js rename to src/prototype/dom/layout.js diff --git a/src/dom/selector.js b/src/prototype/dom/selector.js similarity index 100% rename from src/dom/selector.js rename to src/prototype/dom/selector.js diff --git a/src/lang.js b/src/prototype/lang.js similarity index 95% rename from src/lang.js rename to src/prototype/lang.js index fea6479c0..8a23edfb7 100644 --- a/src/lang.js +++ b/src/prototype/lang.js @@ -1,3 +1,18 @@ +//= compat +//= require "lang/class" +//= require "lang/object" +//= require "lang/function" +//= require "lang/date" +//= require "lang/regexp" +//= require "lang/periodical_executer" +//= require "lang/string" +//= require "lang/template" +//= require "lang/enumerable" +//= require "lang/array" +//= require "lang/hash" +//= require "lang/number" +//= require "lang/range" + /** * == Language == * Additions to JavaScript's "standard library" and extensions to @@ -16,23 +31,23 @@ var Abstract = { }; * * Accepts an arbitrary number of functions and returns the result of the * first one that doesn't throw an error. - * + * * **This method is deprecated.** * *
    More information
    * - * [[Try.these]] provides a simple idiom for trying out blocks of code in - * sequence. Such a sequence of attempts usually represents a downgrading + * [[Try.these]] provides a simple idiom for trying out blocks of code in + * sequence. Such a sequence of attempts usually represents a downgrading * approach to obtaining a given feature. * - * In this example from Prototype's [[Ajax section]] internals, we want to get an - * `XMLHttpRequest` object. Internet Explorer 6 and earlier, however, does not - * provide it as a vanilla JavaScript object, and will throw an error if we - * attempt a simple instantiation. Also, over time, its proprietary way + * In this example from Prototype's [[Ajax section]] internals, we want to get an + * `XMLHttpRequest` object. Internet Explorer 6 and earlier, however, does not + * provide it as a vanilla JavaScript object, and will throw an error if we + * attempt a simple instantiation. Also, over time, its proprietary way * evolved, changing COM interface names. * - * [[Try.these]] will try several ways in sequence, from the best (and, - * theoretically, most widespread) one to the oldest and rarest way, returning + * [[Try.these]] will try several ways in sequence, from the best (and, + * theoretically, most widespread) one to the oldest and rarest way, returning * the result of the first successful function. * * If none of the blocks succeeded, [[Try.these]] will return `undefined`, which @@ -64,17 +79,3 @@ var Try = { return returnValue; } }; - -//= require "lang/class" -//= require "lang/object" -//= require "lang/function" -//= require "lang/date" -//= require "lang/regexp" -//= require "lang/periodical_executer" -//= require "lang/string" -//= require "lang/template" -//= require "lang/enumerable" -//= require "lang/array" -//= require "lang/hash" -//= require "lang/number" -//= require "lang/range" diff --git a/src/lang/array.js b/src/prototype/lang/array.js similarity index 100% rename from src/lang/array.js rename to src/prototype/lang/array.js diff --git a/src/lang/class.js b/src/prototype/lang/class.js similarity index 100% rename from src/lang/class.js rename to src/prototype/lang/class.js diff --git a/src/lang/date.js b/src/prototype/lang/date.js similarity index 100% rename from src/lang/date.js rename to src/prototype/lang/date.js diff --git a/src/lang/enumerable.js b/src/prototype/lang/enumerable.js similarity index 100% rename from src/lang/enumerable.js rename to src/prototype/lang/enumerable.js diff --git a/src/lang/function.js b/src/prototype/lang/function.js similarity index 100% rename from src/lang/function.js rename to src/prototype/lang/function.js diff --git a/src/lang/hash.js b/src/prototype/lang/hash.js similarity index 100% rename from src/lang/hash.js rename to src/prototype/lang/hash.js diff --git a/src/lang/number.js b/src/prototype/lang/number.js similarity index 100% rename from src/lang/number.js rename to src/prototype/lang/number.js diff --git a/src/lang/object.js b/src/prototype/lang/object.js similarity index 100% rename from src/lang/object.js rename to src/prototype/lang/object.js diff --git a/src/lang/periodical_executer.js b/src/prototype/lang/periodical_executer.js similarity index 100% rename from src/lang/periodical_executer.js rename to src/prototype/lang/periodical_executer.js diff --git a/src/lang/range.js b/src/prototype/lang/range.js similarity index 100% rename from src/lang/range.js rename to src/prototype/lang/range.js diff --git a/src/lang/regexp.js b/src/prototype/lang/regexp.js similarity index 100% rename from src/lang/regexp.js rename to src/prototype/lang/regexp.js diff --git a/src/lang/string.js b/src/prototype/lang/string.js similarity index 100% rename from src/lang/string.js rename to src/prototype/lang/string.js diff --git a/src/lang/template.js b/src/prototype/lang/template.js similarity index 100% rename from src/lang/template.js rename to src/prototype/lang/template.js diff --git a/src/prototype/prototype.js b/src/prototype/prototype.js new file mode 100644 index 000000000..7ce3270c4 --- /dev/null +++ b/src/prototype/prototype.js @@ -0,0 +1,183 @@ +//= compat +/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %> + * (c) 2005-2010 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +/** + * Prototype + * + * The [[Prototype]] namespace provides fundamental information about the + * Prototype library you're using, as well as a central repository for default + * iterators or functions. + * + * We say "namespace," because the [[Prototype]] object is not intended for + * instantiation, nor for mixing in other objects. It's really just... a + * namespace. + * + * ##### Your version of Prototype + * + * Your scripts can check against a particular version of Prototype by + * examining [[Prototype.Version]], which is a version [[String]] (e.g. + * "<%= PROTOTYPE_VERSION %>"). The famous + * [script.aculo.us](http://script.aculo.us) library does this at load time to + * ensure it's being used with a reasonably recent version of Prototype, for + * instance. + * + * ##### Browser features + * + * Prototype also provides a (nascent) repository of + * [[Prototype.BrowserFeatures browser feature information]], which it then + * uses here and there in its source code. The idea is, first, to make + * Prototype's source code more readable; and second, to centralize whatever + * scripting trickery might be necessary to detect the browser feature, in + * order to ease maintenance. + * + * ##### Default iterators and functions + * + * Numerous methods in Prototype objects (most notably the [[Enumerable]] + * module) let the user pass in a custom iterator, but make it optional by + * defaulting to an "identity function" (an iterator that just returns its + * argument, untouched). This is the [[Prototype.K]] function, which you'll + * see referred to in many places. + * + * Many methods also take it easy by protecting themselves against missing + * methods here and there, reverting to empty functions when a supposedly + * available method is missing. Such a function simply ignores its potential + * arguments, and does nothing whatsoever (which is, oddly enough, + * blazing fast). The quintessential empty function sits, unsurprisingly, + * at [[Prototype.emptyFunction]] (note the lowercase first letter). +**/ +var Prototype = { + + /** + * Prototype.Version -> String + * + * The version of the Prototype library you are using (e.g. + * "<%= PROTOTYPE_VERSION %>"). + **/ + Version: '<%= PROTOTYPE_VERSION %>', + + /** + * Prototype.Browser + * + * A collection of [[Boolean]] values indicating the browser which is + * currently in use. Available properties are `IE`, `Opera`, `WebKit`, + * `MobileSafari` and `Gecko`. + * + * Example + * + * Prototype.Browser.WebKit; + * //-> true, when executed in any WebKit-based browser. + **/ + Browser: (function(){ + var ua = navigator.userAgent; + // Opera (at least) 8.x+ has "Opera" as a [[Class]] of `window.opera` + // This is a safer inference than plain boolean type conversion of `window.opera` + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), + + /** + * Prototype.BrowserFeatures + * + * A collection of [[Boolean]] values indicating the presence of specific + * browser features. + **/ + BrowserFeatures: { + /** + * Prototype.BrowserFeatures.XPath -> Boolean + * + * Used internally to detect if the browser supports + * [DOM Level 3 XPath](http://www.w3.org/TR/DOM-Level-3-XPath/xpath.html). + **/ + XPath: !!document.evaluate, + + /** + * Prototype.BrowserFeatures.SelectorsAPI -> Boolean + * + * Used internally to detect if the browser supports the + * [NodeSelector API](http://www.w3.org/TR/selectors-api/#nodeselector). + **/ + SelectorsAPI: !!document.querySelector, + + /** + * Prototype.BrowserFeatures.ElementExtensions -> Boolean + * + * Used internally to detect if the browser supports extending html element + * prototypes. + **/ + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + // First, try the named class + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + /** + * Prototype.emptyFunction([argument...]) -> undefined + * - argument (Object): Optional arguments + * + * The [[Prototype.emptyFunction]] does nothing... and returns nothing! + * + * It is used thoughout the framework to provide a fallback function in order + * to cut down on conditionals. Typically you'll find it as a default value + * for optional callback functions. + **/ + emptyFunction: function() { }, + + /** + * Prototype.K(argument) -> argument + * - argument (Object): Optional argument... + * + * [[Prototype.K]] is Prototype's very own + * [identity function](http://en.wikipedia.org/wiki/Identity_function), i.e. + * it returns its `argument` untouched. + * + * This is used throughout the framework, most notably in the [[Enumerable]] + * module as a default value for iterators. + * + * ##### Examples + * + * Prototype.K('hello world!'); + * // -> 'hello world!' + * + * Prototype.K(200); + * // -> 200 + * + * Prototype.K(Prototype.K); + * // -> Prototype.K + **/ + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; diff --git a/src/selector_engine.js b/src/selector_engine.js index 46e296f78..b8deea890 100644 --- a/src/selector_engine.js +++ b/src/selector_engine.js @@ -1,9 +1,11 @@ -Prototype._original_property = window.Sizzle; +//= compat //= require "sizzle" +Prototype._original_property = window.Sizzle; + ;(function(engine) { var extendElements = Prototype.Selector.extendElements; - + function select(selector, scope) { return extendElements(engine(selector, scope || document)); } @@ -11,7 +13,7 @@ Prototype._original_property = window.Sizzle; function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - + Prototype.Selector.engine = engine; Prototype.Selector.select = select; Prototype.Selector.match = match; From b0e5be5832b5db3defc6d7b632f495507b271746 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 3 Dec 2010 02:53:08 -0600 Subject: [PATCH 287/502] Second pass on rewrite. All tests pass in Safari. Lots of failures still in IE. --- src/dom/dom.js | 70 +++++-- src/dom/layout.js | 476 +++++++++++++++++++++++++++++++++++++++++- test/unit/dom_test.js | 124 +++++++---- 3 files changed, 611 insertions(+), 59 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3e7f71825..7eb1bd5db 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -82,7 +82,7 @@ // Define the DOM Level 2 node type constants if they're missing. - if (!Node) GLOBAL.Node = {}; + if (!GLOBAL.Node) GLOBAL.Node = {}; if (!GLOBAL.Node.ELEMENT_NODE) { Object.extend(GLOBAL.Node, { @@ -2112,10 +2112,10 @@ } var descendantOf; - if (DIV.contains && typeof DIV.contains === 'function') { - descendantOf = descendantOf_contains; - } else if (DIV.compareDocumentPosition) { + if (DIV.compareDocumentPosition) { descendantOf = descendantOf_compareDocumentPosition; + } else if (DIV.contains && typeof DIV.contains === 'function') { + descendantOf = descendantOf_contains; } else { descendantOf = descendantOf_DOM; } @@ -2703,7 +2703,7 @@ for (var property in styles) { if (property === 'opacity') { - Element.setOpacity(styles[property]); + Element.setOpacity(element, styles[property]); } else { var value = styles[property]; if (property === 'float' || property === 'cssFloat') { @@ -2820,7 +2820,7 @@ } if (style === 'opacity') - return interpretOpacityValue_IE(element, opacity); + return getOpacity_IE(element); if (value === 'auto') { // If we need a dimension, return null for hidden elements, but return @@ -2833,9 +2833,14 @@ return value; } + function stripAlphaFromFilter_IE(filter) { + return filter.replace(/alpha\([^\)]*\)/gi, ''); + } - function interpretOpacityValue_IE(element, opacity) { - + function hasLayout_IE(element) { + if (!element.currentStyle.hasLayout) + element.style.zoom = 1; + return element; } @@ -2859,14 +2864,38 @@ * element.setStyle({ opacity: 0.5 }); * element.setStyle("opacity: 0.5"); **/ - function setOpacity(element, opacity) { + function setOpacity(element, value) { element = $(element); if (value == 1 || value === '') value = ''; + else if (value < 0.00001) value = 0; + element.style.opacity = value; + return element; + } + + function setOpacity_IE(element, value) { + element = hasLayout($(element)); + var filter = Element.getStyle(element, 'filter'), + style = element.style; + + if (value == 1 || value === '') { + // Remove the `alpha` filter from IE's `filter` CSS property. If there + // is anything left after removal, put it back where it was; otherwise + // remove the property. + filter = stripAlphaFromFilter_IE(filter); + if (filter) style.filter = filter; + else style.removeAttribute('filter'); + return element; + } + if (value < 0.00001) value = 0; - element.style.opacity = value; + + style.filter = stripAlphaFromFilter_IE(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; } + /** * Element.getOpacity(@element) -> String | null * @@ -2876,6 +2905,14 @@ return Element.getStyle(element, 'opacity'); } + function getOpacity_IE(element) { + var filter = Element.getStyle(element, 'filter'); + var match = (filter || '').match(/alpha\(opacity=(.*)\)/); + if (match[1]) return parseFloat(match[1]) / 100; + return 1.0; + } + + Object.extend(methods, { setStyle: setStyle, getStyle: getStyle, @@ -2883,6 +2920,13 @@ getOpacity: getOpacity }); + if ('styleFloat' in DIV.style) { + methods.getStyle = getStyle_IE; + methods.setOpacity = setOpacity_IE; + methods.getOpacity = getOpacity_IE; + } + + // STORAGE var UID = 0; @@ -3326,10 +3370,4 @@ Element.addMethods(methods); - // TODO: move dimensions stuff to layout - // getHeight, getWidth, getDimensions, scrollTo, - // makePositioned, undoPositioned, makeClipping, undoClipping, - // clonePosition - // document.viewport - })(this); \ No newline at end of file diff --git a/src/dom/layout.js b/src/dom/layout.js index d55fd87fd..71027f60a 100644 --- a/src/dom/layout.js +++ b/src/dom/layout.js @@ -823,7 +823,63 @@ **/ function measure(element, property) { return $(element).getLayout().get(property); - } + } + + /** + * Element.getHeight(@element) -> Number + * + * Returns the height of `element`. + * + * This method returns correct values on elements whose display is set to + * `none` either in an inline style rule or in an CSS stylesheet. + * + * For performance reasons, if you need to query both width _and_ height of + * `element`, you should consider using [[Element.getDimensions]] instead. + * + * Note that the value returned is a _number only_ although it is + * _expressed in pixels_. + * + * ##### Examples + * + * language: html + *
    + * + * Then: + * + * $('rectangle').getHeight(); + * // -> 100 + **/ + function getHeight(element) { + return Element.getDimensions(element).height; + } + + /** + * Element.getWidth(@element) -> Number + * + * Returns the width of `element`. + * + * This method returns correct values on elements whose display is set to + * `none` either in an inline style rule or in an CSS stylesheet. + * + * For performance reasons, if you need to query both width _and_ height of + * `element`, you should consider using [[Element.getDimensions]] instead. + * + * Note that the value returned is a _number only_ although it is + * _expressed in pixels_. + * + * ##### Examples + * + * language: html + *
    + * + * Then: + * + * $('rectangle').getWidth(); + * // -> 200 + **/ + function getWidth(element) { + return Element.getDimensions(element).width; + } /** * Element.getDimensions(@element) -> Object @@ -1094,6 +1150,315 @@ if (originalStyles) element.setStyle(originalStyles); return element; } + + + /** + * Element.scrollTo(@element) -> Element + * + * Scrolls the window so that `element` appears at the top of the viewport. + * + * This has a similar effect than what would be achieved using + * [HTML anchors](http://www.w3.org/TR/html401/struct/links.html#h-12.2.3) + * (except the browser's history is not modified). + * + * ##### Example + * + * $(element).scrollTo(); + * // -> Element + **/ + function scrollTo(element) { + element = $(element); + var pos = Element.cumulativeOffset(element); + window.scrollTo(pos.left, pos.top); + return element; + } + + + /** + * Element.makePositioned(@element) -> Element + * + * Allows for the easy creation of a CSS containing block by setting + * `element`'s CSS `position` to `relative` if its initial position is + * either `static` or `undefined`. + * + * To revert back to `element`'s original CSS position, use + * [[Element.undoPositioned]]. + **/ + function makePositioned(element) { + element = $(element); + var position = Element.getStyle(element, 'position'), styles = {}; + if (position === 'static' || !position) { + styles.position = 'relative'; + // When an element is `position: relative` with an undefined `top` and + // `left`, Opera returns the offset relative to positioning context. + if (Prototype.Browser.Opera) { + styles.top = 0; + styles.left = 0; + } + Element.setStyle(element, styles); + Element.store(element, 'prototype_made_positioned', true); + } + return element; + } + + /** + * Element.undoPositioned(@element) -> Element + * + * Sets `element` back to the state it was in _before_ + * [[Element.makePositioned]] was applied to it. + * + * `element`'s absolutely positioned children will now have their positions + * set relatively to `element`'s nearest ancestor with a CSS `position` of + * `'absolute'`, `'relative'` or `'fixed'`. + **/ + function undoPositioned(element) { + element = $(element); + var storage = Element.getStorage(element), + madePositioned = storage.get('prototype_made_positioned'); + + if (madePositioned) { + storage.unset('prototype_made_positioned'); + Element.setStyle(element, { + position: '', + top: '', + bottom: '', + left: '', + right: '' + }); + } + return element; + } + + /** + * Element.makeClipping(@element) -> Element + * + * Simulates the poorly-supported CSS `clip` property by setting `element`'s + * `overflow` value to `hidden`. + * + * To undo clipping, use [[Element.undoClipping]]. + * + * The visible area is determined by `element`'s width and height. + * + * ##### Example + * + * language:html + *
    + * example + *
    + * + * Then: + * + * $('framer').makeClipping().setStyle({width: '100px', height: '100px'}); + * // -> Element + * + * Another example: + * + * language: html + * Click me to try it out. + * + *
    + * example + *
    + * + * + **/ + function makeClipping(element) { + element = $(element); + + var storage = Element.getStorage(element), + madeClipping = storage.get('prototype_made_clipping'); + + if (!madeClipping) { + var overflow = Element.getStyle(element, 'overflow') || 'auto'; + storage.set('prototype_made_clipping', overflow); + if (overflow !== 'hidden') + element.style.overflow = 'hidden'; + } + + return element; + } + + /** + * Element.undoClipping(@element) -> Element + * + * Sets `element`'s CSS `overflow` property back to the value it had + * _before_ [[Element.makeClipping]] was applied. + * + * ##### Example + * + * language: html + *
    + * example + *
    + * + * Then: + * + * $('framer').undoClipping(); + * // -> Element (and sets the CSS overflow property to its original value). + * + * Another example: + * + * language: html + * Click me to try it out. + * + *
    + * example + *
    + * + * + **/ + function undoClipping(element) { + element = $(element); + var storage = Element.getStorage(element), + overflow = storage.get('prototype_made_clipping'); + + if (overflow) { + storage.unset('prototype_made_clipping'); + element.style.overflow = (overflow === 'auto') ? '' : overflow; + } + + return element; + } + + /** + * Element.clonePosition(@element, source[, options]) -> Element + * - source (Element | String): The source element (or its ID). + * - options (Object): The position fields to clone. + * + * Clones the position and/or dimensions of `source` onto the element as + * defined by `options`, with an optional offset for the `left` and `top` + * properties. + * + * Note that the element will be positioned exactly like `source` whether or + * not it is part of the same [CSS containing + * block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details). + * + * ##### Options + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    NameDefaultDescription
    setLefttrueClones source's left CSS property onto element.
    setToptrueClones source's top CSS property onto element.
    setWidthtrueClones source's width onto element.
    setHeighttrueClones source's width onto element.
    offsetLeft0Number by which to offset element's left CSS property.
    offsetTop0Number by which to offset element's top CSS property.
    + **/ + function clonePosition(element, source, options) { + options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, options || {}); + + // Find page position of source. + source = $(source); + element = $(element); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + + // A delta of 0/0 will work for `positioned: fixed` elements, but + // for `position: absolute` we need to get the parent's offset. + if (Element.getStyle(element, 'position') === 'absolute') { + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); + } + + // Adjust by BODY offsets. Fixes some versions of safari. + if (parent === document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + + var layout = Element.getLayout(source); + + // Set position. + var styles = {}; + + if (options.setLeft) + styles.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) + styles.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + + if (options.setWidth) + styles.width = layout.get('border-box-width') + 'px'; + if (options.setHeight) + styles.height = layout.get('border-box-height') + 'px'; + + return Element.setStyle(element, styles); + } + if (Prototype.Browser.IE) { // IE doesn't report offsets correctly for static elements, so we change them @@ -1158,14 +1523,22 @@ Element.addMethods({ getLayout: getLayout, measure: measure, - getDimensions: getDimensions, + getWidth: getWidth, + getHeight: getHeight, + getDimensions: getDimensions, getOffsetParent: getOffsetParent, cumulativeOffset: cumulativeOffset, positionedOffset: positionedOffset, cumulativeScrollOffset: cumulativeScrollOffset, viewportOffset: viewportOffset, absolutize: absolutize, - relativize: relativize + relativize: relativize, + scrollTo: scrollTo, + makePositioned: makePositioned, + undoPositioned: undoPositioned, + makeClipping: makeClipping, + undoClipping: undoClipping, + clonePosition: clonePosition }); function isBody(element) { @@ -1202,6 +1575,101 @@ return new Element.Offset(rect.left - docEl.clientLeft, rect.top - docEl.clientTop); } - }); + }); + } + + +})(); + +(function() { + /** + * document.viewport + * + * The `document.viewport` namespace contains methods that return information + * about the viewport — the rectangle that represents the portion of a web + * page within view. In other words, it's the browser window minus all chrome. + **/ + + var IS_OLD_OPERA = Prototype.Browser.Opera && + (window.parseFloat(window.opera.version()) < 9.5); + var ROOT = null; + function getRootElement() { + if (ROOT) return ROOT; + ROOT = IS_OLD_OPERA ? document.body : document.documentElement; + return ROOT; + } + + /** + * document.viewport.getDimensions() -> Object + * + * Returns an object containing viewport dimensions in the form + * `{ width: Number, height: Number }`. + * + * The _viewport_ is the subset of the browser window that a page occupies + * — the "usable" space in a browser window. + * + * ##### Example + * + * document.viewport.getDimensions(); + * //-> { width: 776, height: 580 } + **/ + function getDimensions() { + return { width: this.getWidth(), height: this.getHeight() }; + } + + /** + * document.viewport.getWidth() -> Number + * + * Returns the width of the viewport. + * + * Equivalent to calling `document.viewport.getDimensions().width`. + **/ + function getWidth() { + return getRootElement().clientWidth; + } + + /** + * document.viewport.getHeight() -> Number + * + * Returns the height of the viewport. + * + * Equivalent to `document.viewport.getDimensions().height`. + **/ + function getHeight() { + return getRootElement().clientHeight; + } + + /** + * document.viewport.getScrollOffsets() -> Array + * + * Returns the viewport's horizontal and vertical scroll offsets. + * + * Returns an array in the form of `[leftValue, topValue]`. Also accessible + * as properties: `{ left: leftValue, top: topValue }`. + * + * ##### Examples + * + * document.viewport.getScrollOffsets(); + * //-> { left: 0, top: 0 } + * + * window.scrollTo(0, 120); + * document.viewport.getScrollOffsets(); + * //-> { left: 0, top: 120 } + **/ + function getScrollOffsets() { + var x = window.pageXOffset || document.documentElement.scrollLeft || + document.body.scrollLeft; + var y = window.pageYOffset || document.documentElement.scrollTop || + document.body.scrollTop; + + return new Element.Offset(x, y); } + + document.viewport = { + getDimensions: getDimensions, + getWidth: getWidth, + getHeight: getHeight, + getScrollOffsets: getScrollOffsets + }; + })(); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index aa173825f..f4b32aa62 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -61,10 +61,14 @@ new Test.Unit.Runner({ testDollarFunction: function() { this.assertUndefined($()); - this.assertNull(document.getElementById('noWayThisIDExists')); - this.assertNull($('noWayThisIDExists')); + this.assertNull(document.getElementById('noWayThisIDExists'), + 'nonexistent ID should return null from getElementById'); + this.assertNull($('noWayThisIDExists'), + 'nonexistent ID should return null from $'); - this.assertIdentical(document.getElementById('testdiv'), $('testdiv')); + this.assertIdentical(document.getElementById('testdiv'), $('testdiv'), + 'getElementById and $ should return the same element'); + this.assertEnumEqual([ $('testdiv'), $('container') ], $('testdiv', 'container')); this.assertEnumEqual([ $('testdiv'), undefined, $('container') ], $('testdiv', 'noWayThisIDExists', 'container')); @@ -162,17 +166,26 @@ new Test.Unit.Runner({ }, testElementInsertInTables: function() { - Element.insert('second_row', {after:'Third Row'}); - this.assert($('second_row').descendantOf('table')); + Element.insert('second_row', { after:'Third Row' }); + + this.assert($('second_row').parentNode == $('table'), + 'table rows should be inserted correctly'); - $('a_cell').insert({top:'hello world'}); - this.assert($('a_cell').innerHTML.startsWith('hello world')); - $('a_cell').insert({after:'hi planet'}); - this.assertEqual('hi planet', $('a_cell').next().innerHTML); + $('a_cell').insert({ top: 'hello world' }); + this.assert($('a_cell').innerHTML.startsWith('hello world'), + 'content should be inserted into table cells correctly'); + + $('a_cell').insert({ after: 'hi planet'}); + this.assertEqual('hi planet', $('a_cell').next().innerHTML, + 'table cells should be inserted after existing table cells correctly'); + $('table_for_insertions').insert('a cell!'); - this.assert($('table_for_insertions').innerHTML.gsub('\r\n', '').toLowerCase().include('a cell!')); - $('row_1').insert({after:'last'}); - this.assertEqual('last', $A($('table_for_row_insertions').getElementsByTagName('tr')).last().lastChild.innerHTML); + this.assert($('table_for_insertions').innerHTML.gsub('\r\n', '').toLowerCase().include('a cell!'), + 'complex content should be inserted into a table correctly'); + + $('row_1').insert({ after:'last' }); + this.assertEqual('last', $A($('table_for_row_insertions').getElementsByTagName('tr')).last().lastChild.innerHTML, + 'complex content should be inserted after a table row correctly'); }, testElementInsertInSelect: function() { @@ -810,17 +823,22 @@ new Test.Unit.Runner({ $('style_test_3').setStyle({ cssFloat: 'none' }); this.assertEqual('none', $('style_test_3').getStyle('float')); - this.assertEqual(1, $('style_test_3').getStyle('opacity')); + this.assertEqual(1, $('style_test_3').getStyle('opacity'), + '#style_test_3 opacity should be 1'); $('style_test_3').setStyle({ opacity: 0.5 }); this.assertEqual(0.5, $('style_test_3').getStyle('opacity')); + window.debug = true; $('style_test_3').setStyle({ opacity: '' }); - this.assertEqual(1, $('style_test_3').getStyle('opacity')); + this.assertEqual(1, $('style_test_3').getStyle('opacity'), + '#style_test_3 opacity should be 1'); + window.debug = false; $('style_test_3').setStyle({ opacity: 0 }); - this.assertEqual(0, $('style_test_3').getStyle('opacity')); - + this.assertEqual(0, $('style_test_3').getStyle('opacity'), + '#style_test_3 opacity should be 0'); + $('test_csstext_1').setStyle('font-size: 15px'); this.assertEqual('15px', $('test_csstext_1').getStyle('font-size')); @@ -930,8 +948,11 @@ new Test.Unit.Runner({ Element.getStyle('style_test_2','margin-left')); ['not_floating_none','not_floating_style','not_floating_inline'].each(function(element) { - this.assertEqual('none', $(element).getStyle('float')); - this.assertEqual('none', $(element).getStyle('cssFloat')); + + this.assertEqual('none', $(element).getStyle('float'), + 'float on ' + element); + this.assertEqual('none', $(element).getStyle('cssFloat'), + 'cssFloat on ' + element); }, this); ['floating_style','floating_inline'].each(function(element) { @@ -939,20 +960,20 @@ new Test.Unit.Runner({ this.assertEqual('left', $(element).getStyle('cssFloat')); }, this); - this.assertEqual(0.5, $('op1').getStyle('opacity')); - this.assertEqual(0.5, $('op2').getStyle('opacity')); - this.assertEqual(1.0, $('op3').getStyle('opacity')); + this.assertEqual(0.5, $('op1').getStyle('opacity'), 'get opacity on #op1'); + this.assertEqual(0.5, $('op2').getStyle('opacity'), 'get opacity on #op2'); + this.assertEqual(1.0, $('op3').getStyle('opacity'), 'get opacity on #op3'); $('op1').setStyle({opacity: '0.3'}); $('op2').setStyle({opacity: '0.3'}); $('op3').setStyle({opacity: '0.3'}); - this.assertEqual(0.3, $('op1').getStyle('opacity')); - this.assertEqual(0.3, $('op2').getStyle('opacity')); - this.assertEqual(0.3, $('op3').getStyle('opacity')); + this.assertEqual(0.3, $('op1').getStyle('opacity'), 'get opacity on #op1'); + this.assertEqual(0.3, $('op2').getStyle('opacity'), 'get opacity on #op2'); + this.assertEqual(0.3, $('op3').getStyle('opacity'), 'get opacity on #op3'); $('op3').setStyle({opacity: 0}); - this.assertEqual(0, $('op3').getStyle('opacity')); + this.assertEqual(0, $('op3').getStyle('opacity'), 'get opacity on #op3'); if(navigator.appVersion.match(/MSIE/)) { this.assertEqual('alpha(opacity=30)', $('op1').getStyle('filter')); @@ -1526,16 +1547,27 @@ new Test.Unit.Runner({ // add observer on a child element.down('span').observe('dblclick', Prototype.emptyFunction); + element.store('foo', 'bar'); + element.down('span').store('baz', 'thud'); + var shallowClone = element.clone(); var deepClone = element.clone(true); var assertCloneTraits = (function(clone) { - this.assert(clone); // exists - this.assert(clone.show); // is extended - this.assertEqual('DIV', clone.nodeName.toUpperCase()); // proper nodeName - this.assertEqual('foo', clone.className); // proper attributes - this.assertEqual('bar', clone.title); - this.assert(!clone._prototypeUID); // _prototypeUID does not exist + this.assert(clone, 'clone should exist'); + this.assert(clone.show, 'clone should be extended'); + this.assertEqual('DIV', clone.nodeName.toUpperCase(), + 'clone should have proper tag name'); + this.assertEqual('foo', clone.className, + 'clone should have proper attributes'); + this.assertEqual('bar', clone.title, + 'clone should have proper title'); + + this.assertEqual( + clone.retrieve('foo', false), + false, + 'clone should not share storage with original' + ); }).bind(this); // test generic traits of both deep and shallow clones first @@ -1543,9 +1575,15 @@ new Test.Unit.Runner({ assertCloneTraits(deepClone); // test deep clone traits - this.assert(deepClone.firstChild); - this.assertEqual('SPAN', deepClone.firstChild.nodeName.toUpperCase()); - this.assert(!deepClone.down('span')._prototypeUID); + this.assert(deepClone.firstChild, + 'deep clone should have children'); + this.assertEqual('SPAN', deepClone.firstChild.nodeName.toUpperCase(), + "deep clone's children should have proper tag name"); + this.assertEqual( + deepClone.down('span').retrieve('baz', false), + false, + "deep clone's child should not share storage with original's child" + ); }, testElementPurge: function() { @@ -1555,10 +1593,15 @@ new Test.Unit.Runner({ var uid = element._prototypeUID; this.assert(uid in Element.Storage, "newly-created element's uid should exist in `Element.Storage`"); + var storageKeysBefore = Object.keys(Element.Storage).length; element.purge(); + var storageKeysAfter = Object.keys(Element.Storage).length; - this.assert(!(uid in Element.Storage), "purged element's UID should no longer exist in `Element.Storage`"); - this.assert(!(Object.isNumber(element._prototypeUID)), "purged element's UID should no longer exist as expando on element"); + this.assertEqual( + storageKeysAfter, + storageKeysBefore - 1, + "purged element's UID should no longer exist in `Element.Storage`" + ); // Should purge elements replaced via innerHTML. var parent = new Element('div'); @@ -1571,11 +1614,14 @@ new Test.Unit.Runner({ child.observe('click', function(event) { trigger = true; }); var childUID = child._prototypeUID; + storageKeysBefore = Object.keys(Element.Storage).length; parent.update(""); - + storageKeysAfter = Object.keys(Element.Storage).length; + // At this point, `child` should have been purged. - this.assert( - !(childUID in Element.Storage), + this.assertEqual( + storageKeysAfter, + storageKeysBefore - 1, "purged element's UID should no longer exist in `Element.Storage`" ); From 5cd09c7f7d5973d10c0d3c118e8c46b1feb7b299 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 6 Dec 2010 07:35:56 -0600 Subject: [PATCH 288/502] Add private convenience method for purging an element array or NodeList all at once. --- src/dom/dom.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/dom/dom.js b/src/dom/dom.js index 7eb1bd5db..eaf9cc8cf 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1152,6 +1152,27 @@ } } + function purgeCollection(elements) { + var i = elements.length; + while (i--) + purgeElement(elements[i]); + } + + function purgeCollection_IE(elements) { + var i = elements.length, element, eventName, responders, uid, j; + while (i--) { + element = elements[i]; + uid = getUniqueElementID(element); + delete Element.Storage[uid]; + delete Event.cache[uid]; + } + } + + if (!window.addEventListener && window.attachEvent) { + purgeCollection = purgeCollection_IE; + } + + /** * Element.purge(@element) -> null * From e5c8fcead6331b7ca2a2eddc735b07a784afa3d6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 6 Dec 2010 07:36:59 -0600 Subject: [PATCH 289/502] Various fixes. --- src/dom/dom.js | 63 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index eaf9cc8cf..02f5b80e5 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -1755,9 +1755,12 @@ // Element#descendants (and therefore extend all nodes). function down_IE(element, expression, index) { element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? _descendants(element)[expression] : + if (arguments.length === 1) + return Element.firstDescendant(element); + + var node = Object.isNumber(expression) ? _descendants(element)[expression] : Element.select(element, expression)[index || 0]; + return Element.extend(node); } if (!Prototype.BrowserFeatures.ElementExtensions) @@ -2115,7 +2118,7 @@ * $('homo-erectus').descendantOf('homo-sapiens'); * // -> false **/ - function descendantOf_DOM(element) { + function descendantOf_DOM(element, ancestor) { element = $(element); while (element = element.parentNode) if (element === ancestor) return true; @@ -2124,6 +2127,8 @@ function descendantOf_contains(element, ancestor) { element = $(element), ancestor = $(ancestor); + // Some nodes, like `document`, don't have the "contains" method. + if (!ancestor.contains) return descendantOf_DOM(element, ancestor); return ancestor.contains(element) && ancestor !== element; } @@ -2135,7 +2140,7 @@ var descendantOf; if (DIV.compareDocumentPosition) { descendantOf = descendantOf_compareDocumentPosition; - } else if (DIV.contains && typeof DIV.contains === 'function') { + } else if (DIV.contains) { descendantOf = descendantOf_contains; } else { descendantOf = descendantOf_DOM; @@ -2311,6 +2316,20 @@ return element.getAttribute(attribute); } + var PROBLEMATIC_ATTRIBUTE_READING = (function() { + DIV.setAttribute('onclick', Prototype.emptyFunction); + var value = DIV.getAttribute('onclick'); + var isFunction = (typeof value === 'function'); + DIV.removeAttribute('onclick'); + return isFunction; + })(); + + if (PROBLEMATIC_ATTRIBUTE_READING) { + readAttribute = readAttribute_IE; + } else if (Prototype.Browser.Opera) { + readAttribute = readAttribute_Opera; + } + /** * Element.writeAttribute(@element, attribute[, value = true]) -> Element @@ -2659,15 +2678,16 @@ // STYLES function normalizeStyleName(style) { - if (style === 'float') return 'cssFloat'; + if (style === 'float' || style === 'styleFloat') + return 'cssFloat'; return style.camelize(); } function normalizeStyleName_IE(style) { - if (style === 'float') return 'styleFloat'; + if (style === 'float' || style === 'cssFloat') + return 'styleFloat'; return style.camelize(); } - /** * Element.setStyle(@element, styles) -> Element @@ -2855,7 +2875,7 @@ } function stripAlphaFromFilter_IE(filter) { - return filter.replace(/alpha\([^\)]*\)/gi, ''); + return (filter || '').replace(/alpha\([^\)]*\)/gi, ''); } function hasLayout_IE(element) { @@ -2894,9 +2914,9 @@ } function setOpacity_IE(element, value) { - element = hasLayout($(element)); + element = hasLayout_IE($(element)); var filter = Element.getStyle(element, 'filter'), - style = element.style; + style = element.style; if (value == 1 || value === '') { // Remove the `alpha` filter from IE's `filter` CSS property. If there @@ -2904,7 +2924,7 @@ // remove the property. filter = stripAlphaFromFilter_IE(filter); if (filter) style.filter = filter; - else style.removeAttribute('filter'); + else style.removeAttribute('filter'); return element; } @@ -2928,6 +2948,7 @@ function getOpacity_IE(element) { var filter = Element.getStyle(element, 'filter'); + if (filter.length === 0) return 1.0; var match = (filter || '').match(/alpha\(opacity=(.*)\)/); if (match[1]) return parseFloat(match[1]) / 100; return 1.0; @@ -2946,13 +2967,11 @@ methods.setOpacity = setOpacity_IE; methods.getOpacity = getOpacity_IE; } - - // STORAGE var UID = 0; - GLOBAL.Element.Storage = {}; + GLOBAL.Element.Storage = { UID: 0 }; function getUniqueElementID(element) { if (element === window) return 0; @@ -2960,14 +2979,17 @@ // Need to use actual `typeof` operator to prevent errors in some // environments when accessing node expandos. if (typeof element._prototypeUID === 'undefined') - element._prototypeUID = UID++; + element._prototypeUID = Element.Storage.UID++; return element._prototypeUID; } // In Internet Explorer, DOM nodes have a `uniqueID` property. Saves us // from inventing our own. function getUniqueElementID_IE(element) { - return element === window ? 0 : element.uniqueID; + if (element === window) return 0; + // The document object's `uniqueID` property changes each time you read it. + if (element == document) return 1; + return element.uniqueID; } var HAS_UNIQUE_ID_PROPERTY = ('uniqueID' in DIV); @@ -3139,9 +3161,12 @@ return element; } - - if (F.SpecificElementExtensions && HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) - extend = extend_IE8; + + // If the browser lets us extend specific elements, we can replace `extend` + // with a thinner version (or, ideally, an empty version). + if (F.SpecificElementExtensions) { + extend = HTMLOBJECTELEMENT_PROTOTYPE_BUGGY ? extend_IE8 : Prototype.K; + } function addMethodsToTagName(tagName, methods) { tagName = tagName.toUpperCase(); From c21b3191c172d003eec2a6046cc1924a8002c347 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 6 Dec 2010 07:41:06 -0600 Subject: [PATCH 290/502] Massive rewrite of event observation. Create responders from a separate closure free of DOM objects, thereby preventing memory leaks and removing the need to unregister event listeners before unloading the page. --- src/dom/event.js | 602 +++++++++++++++++++++++++++++------------------ 1 file changed, 369 insertions(+), 233 deletions(-) diff --git a/src/dom/event.js b/src/dom/event.js index 4d0ef9fe9..75b418bde 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -1,5 +1,4 @@ -(function() { - +(function(GLOBAL) { /** section: DOM * class Event * @@ -63,6 +62,11 @@ * different method of event registration, for whatever reason,you'll need to * extend these events manually with [[Event.extend]]. **/ + var DIV = document.createElement('div'); + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, @@ -77,23 +81,16 @@ KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: {} + KEY_INSERT: 45 }; - - var docEl = document.documentElement; - var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl - && 'onmouseleave' in docEl; - - + // We need to support three different event "modes": // 1. browsers with only DOM L2 Events (WebKit, FireFox); // 2. browsers with only IE's legacy events system (IE 6-8); // 3. browsers with _both_ systems (IE 9 and arguably Opera). // // Groups 1 and 2 are easy; group three is trickier. - + var isIELegacyEvent = function(event) { return false; }; if (window.attachEvent) { @@ -115,16 +112,16 @@ // The two systems have different ways of indicating which button was used // for a mouse event. var _isButton; - + function _isButtonForDOMEvents(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); } - + var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; function _isButtonForLegacyEvents(event, code) { return event.button === legacyButtonMap[code]; } - + // In WebKit we have to account for when the user holds down the "meta" key. function _isButtonForWebKit(event, code) { switch (code) { @@ -134,7 +131,7 @@ default: return false; } } - + if (window.attachEvent) { if (!window.addEventListener) { // Legacy IE events only. @@ -151,7 +148,7 @@ } else { _isButton = _isButtonForDOMEvents; } - + /** * Event.isLeftClick(@event) -> Boolean * @@ -183,7 +180,7 @@ * report clicks of the _left_ button as "left-clicks." **/ function isRightClick(event) { return _isButton(event, 2) } - + /** deprecated * Event.element(@event) -> Element * - event (Event): An Event object @@ -449,7 +446,7 @@ // IE's method for extending events. Event.extend = function(event, element) { if (!event) return false; - + // If it's not a legacy event, it doesn't need extending. if (!isIELegacyEvent(event)) return event; @@ -484,108 +481,117 @@ Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); } - - function _createResponder(element, eventName, handler) { - // We don't set a default on the call to Element#retrieve so that we can - // handle the element's "virgin" state. - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) { - // First time we've handled this element. Put it into the cache. - CACHE.push(element); - registry = Element.retrieve(element, 'prototype_event_registry', $H()); - } - - var respondersForEvent = registry.get(eventName); - if (Object.isUndefined(respondersForEvent)) { - respondersForEvent = []; - registry.set(eventName, respondersForEvent); - } - - // Work around the issue that permits a handler to be attached more than - // once to the same element & event type. - if (respondersForEvent.pluck('handler').include(handler)) return false; - - var responder; - if (eventName.include(":")) { - // Custom event. - responder = function(event) { - // If it's not a custom event, ignore it. - if (Object.isUndefined(event.eventName)) - return false; - - // If it's a custom event, but not the _correct_ custom event, ignore it. - if (event.eventName !== eventName) - return false; - - Event.extend(event, element); - handler.call(element, event); - }; - } else { - // Non-custom event. - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && - (eventName === "mouseenter" || eventName === "mouseleave")) { - // If we're dealing with mouseenter or mouseleave in a non-IE browser, - // we create a custom responder that mimics their behavior within - // mouseover and mouseout. - if (eventName === "mouseenter" || eventName === "mouseleave") { - responder = function(event) { - Event.extend(event, element); - - var parent = event.relatedTarget; - while (parent && parent !== element) { - try { parent = parent.parentNode; } - catch(e) { parent = element; } - } - - if (parent === element) return; - - handler.call(element, event); - }; - } - } else { - responder = function(event) { - Event.extend(event, element); - handler.call(element, event); - }; - } - } - - responder.handler = handler; - respondersForEvent.push(responder); - return responder; + + // + // EVENT REGISTRY + // + var EVENT_TRANSLATIONS = { + mouseenter: 'mouseover', + mouseleave: 'mouseout' + }; + + function getDOMEventName(eventName) { + return EVENT_TRANSLATIONS[eventName] || eventName; } - - function _destroyCache() { - for (var i = 0, length = CACHE.length; i < length; i++) { - Event.stopObserving(CACHE[i]); - CACHE[i] = null; - } + + if (MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) + getDOMEventName = Prototype.K; + + function getUniqueElementID(element) { + if (element === window) return 0; + + // Need to use actual `typeof` operator to prevent errors in some + // environments when accessing node expandos. + if (typeof element._prototypeUID === 'undefined') + element._prototypeUID = Element.Storage.UID++; + return element._prototypeUID; } + + // In Internet Explorer, DOM nodes have a `uniqueID` property. Saves us + // from inventing our own. + function getUniqueElementID_IE(element) { + if (element === window) return 0; + // The document object's `uniqueID` property changes each time you read it. + if (element == document) return 1; + return element.uniqueID; + } + + if ('uniqueID' in DIV) + getUniqueElementID = getUniqueElementID_IE; - var CACHE = []; - - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (Prototype.Browser.IE) - window.attachEvent('onunload', _destroyCache); - - // Safari needs a dummy event handler on page unload so that it won't - // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" - // object when page is returned to via the back button using its bfcache. - if (Prototype.Browser.WebKit) - window.addEventListener('unload', Prototype.emptyFunction, false); - + function isCustomEvent(eventName) { + return eventName.include(':'); + } - var _getDOMEventName = Prototype.K, - translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + Event._isCustomEvent = isCustomEvent; - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { - _getDOMEventName = function(eventName) { - return (translations[eventName] || eventName); + function getRegistryForElement(element) { + var CACHE = GLOBAL.Event.cache; + var uid = getUniqueElementID(element); + if (!CACHE[uid]) CACHE[uid] = { element: element }; + return CACHE[uid]; + } + + function destroyRegistryForElement(element) { + var uid = getUniqueElementID(element); + delete GLOBAL.Event.cache[uid]; + } + + // The `register` and `unregister` functions handle creating the responder + // and managing an event registry. They _don't_ attach and detach the + // listeners themselves. + + // Add an event to the element's event registry. + function register(element, eventName, handler) { + var registry = getRegistryForElement(element); + if (!registry[eventName]) registry[eventName] = []; + var entries = registry[eventName]; + + // Make sure this handler isn't already attached. + var i = entries.length; + while (i--) + if (entries[i].handler === handler) return null; + + var uid = getUniqueElementID(element); + var responder = GLOBAL.Event._createResponder(uid, eventName, handler); + var entry = { + responder: responder, + handler: handler }; + + entries.push(entry); + return entry; } + + // Remove an event from the element's event registry. + function unregister(element, eventName, handler) { + var registry = getRegistryForElement(element); + var entries = registry[eventName]; + if (!entries) return; + + var i = entries.length, entry; + while (i--) { + if (entries[i].handler === handler) { + entry = entries[i]; + break; + } + } + + // This handler wasn't in the collection, so it doesn't need to be + // unregistered. + if (!entry) return; + // Remove the entry from the collection; + var index = entries.indexOf(entry); + entries.splice(index, 1); + + return entry; + } + + + // + // EVENT OBSERVING + // /** * Event.observe(element, eventName, handler) -> Element * - element (Element | String): The DOM element to observe, or its ID. @@ -736,37 +742,42 @@ * 1.6 also introduced setting the `this` context to the element being * observed, automatically extending the [[Event]] object, and the * [[Event#findElement]] method. - **/ + **/ function observe(element, eventName, handler) { element = $(element); + var entry = register(element, eventName, handler); + + if (entry === null) return element; - var responder = _createResponder(element, eventName, handler); - - if (!responder) return element; - - if (eventName.include(':')) { - // Custom event. - if (element.addEventListener) - element.addEventListener("dataavailable", responder, false); - else { - // We observe two IE-proprietarty events: one for custom events that - // bubble and one for custom events that do not bubble. - element.attachEvent("ondataavailable", responder); - element.attachEvent("onlosecapture", responder); - } + var responder = entry.responder; + if (isCustomEvent(eventName)) + observeCustomEvent(element, eventName, responder); + else + observeStandardEvent(element, eventName, responder); + + return element; + } + + function observeStandardEvent(element, eventName, responder) { + var actualEventName = getDOMEventName(eventName); + if (element.addEventListener) { + element.addEventListener(actualEventName, responder, false); } else { - var actualEventName = _getDOMEventName(eventName); - - // Ordinary event. - if (element.addEventListener) - element.addEventListener(actualEventName, responder, false); - else - element.attachEvent("on" + actualEventName, responder); + element.attachEvent('on' + actualEventName, responder); } - - return element; } - + + function observeCustomEvent(element, eventName, responder) { + if (element.addEventListener) { + element.addEventListener('dataavailable', responder, false); + } else { + // We observe two IE-proprietarty events: one for custom events that + // bubble and one for custom events that do not bubble. + element.attachEvent('ondataavailable', responder); + element.attachEvent('onlosecapture', responder); + } + } + /** * Event.stopObserving(element[, eventName[, handler]]) -> Element * - element (Element | String): The element to stop observing, or its ID. @@ -828,66 +839,97 @@ * ...and then to remove: * * $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right - **/ + **/ function stopObserving(element, eventName, handler) { element = $(element); - - var registry = Element.retrieve(element, 'prototype_event_registry'); - if (!registry) return element; - - if (!eventName) { - // We stop observing all events. - // e.g.: $(element).stopObserving(); - registry.each( function(pair) { - var eventName = pair.key; - stopObserving(element, eventName); - }); + var handlerGiven = !Object.isUndefined(handler), + eventNameGiven = !Object.isUndefined(eventName); + + if (!eventNameGiven && !handlerGiven) { + stopObservingElement(element); return element; } - - var responders = registry.get(eventName); - if (!responders) return element; - - if (!handler) { - // We stop observing all handlers for the given eventName. - // e.g.: $(element).stopObserving('click'); - responders.each(function(r) { - stopObserving(element, eventName, r.handler); - }); + + if (!handlerGiven) { + stopObservingEventName(element, eventName); return element; } - var i = responders.length, responder; - while (i--) { - if (responders[i].handler === handler) { - responder = responders[i]; - break; - } + var entry = unregister(element, eventName, handler); + + if (!entry) return element; + removeEvent(element, eventName, entry.responder); + return element; + } + + function stopObservingStandardEvent(element, eventName, responder) { + var actualEventName = getDOMEventName(eventName); + if (element.removeEventListener) { + element.removeEventListener(actualEventName, responder, false); + } else { + element.detachEvent('on' + actualEventName, responder); } - if (!responder) return element; - - if (eventName.include(':')) { - // Custom event. - if (element.removeEventListener) - element.removeEventListener("dataavailable", responder, false); - else { - element.detachEvent("ondataavailable", responder); - element.detachEvent("onlosecapture", responder); - } + } + + function stopObservingCustomEvent(element, eventName, responder) { + if (element.removeEventListener) { + element.removeEventListener('dataavailable', responder, false); } else { - // Ordinary event. - var actualEventName = _getDOMEventName(eventName); - if (element.removeEventListener) - element.removeEventListener(actualEventName, responder, false); - else - element.detachEvent('on' + actualEventName, responder); + element.detachEvent('ondataavailable', responder); + element.detachEvent('onlosecapture', responder); } + } + - registry.set(eventName, responders.without(responder)); + // The `stopObservingElement` and `stopObservingEventName` functions are + // for bulk removal of event listeners. We use them rather than recurse + // back into `stopObserving` to avoid touching the registry more often than + // necessary. + + // Stop observing _all_ listeners on an element. + function stopObservingElement(element) { + var registry = getRegistryForElement(element); + destroyRegistryForElement(element); + + var entries, i; + for (var eventName in registry) { + entries = registry[eventName]; + i = entries.length; + while (i--) + removeEvent(element, eventName, entries[i].responder); + } + } + + // Stop observing all listeners of a certain event name on an element. + function stopObservingEventName(element, eventName) { + var registry = getRegistryForElement(element); + var entries = registry[eventName]; + if (!entries) return; + delete registry[eventName]; + + var i = entries.length; + while (i--) + removeEvent(element, eventName, entries[i].responder); + } + + function removeEvent(element, eventName, handler) { + if (isCustomEvent(eventName)) + stopObservingCustomEvent(element, eventName, handler); + else + stopObservingStandardEvent(element, eventName, handler); + } + + + + // FIRING CUSTOM EVENTS + function getFireTarget(element) { + if (element !== document) return element; + if (document.createEvent && !element.dispatchEvent) + return document.documentElement; return element; } - + /** * Event.fire(element, eventName[, memo[, bubble = true]]) -> Event * - memo (?): Metadata for the event. Will be accessible to event @@ -899,34 +941,41 @@ * Custom events **must** include a colon (`:`) in their names. **/ function fire(element, eventName, memo, bubble) { - element = $(element); - - if (Object.isUndefined(bubble)) - bubble = true; - - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent('HTMLEvents'); - event.initEvent('dataavailable', bubble, true); - } else { - event = document.createEventObject(); - event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) - element.dispatchEvent(event); - else - element.fireEvent(event.eventType, event); - + element = getFireTarget($(element)); + if (Object.isUndefined(bubble)) bubble = true; + memo = memo || {}; + + var event = fireEvent(element, eventName, memo, bubble); return Event.extend(event); } + function fireEvent_DOM(element, eventName, memo, bubble) { + var event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', bubble, true); + + event.eventName = eventName; + event.memo = memo; + + element.dispatchEvent(event); + return event; + } + + function fireEvent_IE(element, eventName, memo, bubble) { + var event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; + + event.eventName = eventName; + event.memo = memo; + + element.fireEvent(event.eventType, event); + return event; + } + + var fireEvent = document.createEvent ? fireEvent_DOM : fireEvent_IE; + + + // EVENT DELEGATION + /** * class Event.Handler * @@ -965,6 +1014,7 @@ this.callback = callback; this.handler = this.handleEvent.bind(this); }, + /** * Event.Handler#start -> Event.Handler @@ -975,7 +1025,7 @@ Event.observe(this.element, this.eventName, this.handler); return this; }, - + /** * Event.Handler#stop -> Event.Handler * @@ -985,7 +1035,7 @@ Event.stopObserving(this.element, this.eventName, this.handler); return this; }, - + handleEvent: function(event) { var element = Event.findElement(event, this.selector); if (element) this.callback.call(this.element, event, element); @@ -1065,7 +1115,7 @@ return new Event.Handler(element, eventName, selector, callback).start(); } - + Object.extend(Event, Event.Methods); Object.extend(Event, { @@ -1218,47 +1268,133 @@ }); // Export to the global scope. - if (window.Event) Object.extend(window.Event, Event); - else window.Event = Event; -})(); + if (GLOBAL.Event) Object.extend(window.Event, Event); + else GLOBAL.Event = Event; + + GLOBAL.Event.cache = {}; + + function destroyCache_IE() { + GLOBAL.Event.cache = null; + } + + if (window.attachEvent) + window.attachEvent('onunload', destroyCache_IE); + + DIV = null; + docEl = null; +})(this); -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ +(function(GLOBAL) { + /* Code for creating leak-free event responders is based on work by + John-David Dalton. */ + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + function isSimulatedMouseEnterLeaveEvent(eventName) { + return MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === 'mouseenter' || eventName === 'mouseleave'); + } + + // The functions for creating responders accept the element's UID rather + // than the element itself. This way, there are _no_ DOM objects inside the + // closure we create, meaning there's no need to unregister event listeners + // on unload. + function createResponder(uid, eventName, handler) { + if (Event._isCustomEvent(eventName)) + return createResponderForCustomEvent(uid, eventName, handler); + if (isSimulatedMouseEnterLeaveEvent(eventName)) + return createMouseEnterLeaveResponder(uid, eventName, handler); + + return function(event) { + var cacheEntry = Event.cache[uid]; + var element = cacheEntry.element; - var timer; + Event.extend(event, element); + handler.call(element, event); + }; + } + + function createResponderForCustomEvent(uid, eventName, handler) { + return function(event) { + var cacheEntry = Event.cache[uid], element = cacheEntry.element; + + if (Object.isUndefined(event.eventName)) + return false; + + if (event.eventName !== eventName) + return false; + + Event.extend(event, element); + handler.call(element, event); + }; + } + + function createMouseEnterLeaveResponder(uid, eventName, handler) { + return function(event) { + var cacheEntry = Event.cache[uid], element = cacheEntry.element; + + Event.extend(event, element); + var parent = event.relatedTarget; + + // Walk up the DOM tree to see if the related target is a descendant of + // the original element. If it is, we ignore the event to match the + // behavior of mouseenter/mouseleave. + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } + + if (parent === element) return; + handler.call(element, event); + } + } + + GLOBAL.Event._createResponder = createResponder; + docEl = null; +})(this); +(function(GLOBAL) { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ + + var TIMER; + function fireContentLoadedEvent() { if (document.loaded) return; - if (timer) window.clearTimeout(timer); + if (TIMER) window.clearTimeout(TIMER); document.loaded = true; document.fire('dom:loaded'); } - + function checkReadyState() { if (document.readyState === 'complete') { - document.stopObserving('readystatechange', checkReadyState); + document.detachEvent('onreadystatechange', checkReadyState); fireContentLoadedEvent(); } } - + function pollDoScroll() { - try { document.documentElement.doScroll('left'); } - catch(e) { - timer = pollDoScroll.defer(); + try { + document.documentElement.doScroll('left'); + } catch (e) { + TIMER = pollDoScroll.defer(); return; } + fireContentLoadedEvent(); } - + if (document.addEventListener) { + // All browsers that support DOM L2 Events support DOMContentLoaded, + // including IE 9. document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { - document.observe('readystatechange', checkReadyState); - if (window == top) - timer = pollDoScroll.defer(); + document.attachEvent('onreadystatechange', checkReadyState); + if (window == top) TIMER = pollDoScroll.defer(); } - - // Worst-case fallback + + // Worst-case fallback. Event.observe(window, 'load', fireContentLoadedEvent); -})(); +})(this); From afdb2c0084334432401b1cba93c6573451d5e910 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 6 Dec 2010 07:42:12 -0600 Subject: [PATCH 291/502] Add some messages to test assertions in dom_test.js. --- test/unit/dom_test.js | 51 +++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index f4b32aa62..738c5431b 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -502,12 +502,17 @@ new Test.Unit.Runner({ testElementIdentify: function() { var parent = $('identification'); - this.assertEqual(parent.down().identify(), 'predefined_id'); - this.assert(parent.down(1).identify().startsWith('anonymous_element_')); - this.assert(parent.down(2).identify().startsWith('anonymous_element_')); - this.assert(parent.down(3).identify().startsWith('anonymous_element_')); - - this.assert(parent.down(3).id !== parent.down(2).id); + this.assertEqual(parent.down().identify(), 'predefined_id', + "identify should preserve the IDs of elements that already have them"); + this.assert(parent.down(1).identify().startsWith('anonymous_element_'), + "should have #anonymous_element_1"); + this.assert(parent.down(2).identify().startsWith('anonymous_element_'), + "should have #anonymous_element_2"); + this.assert(parent.down(3).identify().startsWith('anonymous_element_'), + "should have #anonymous_element_3"); + + this.assert(parent.down(3).id !== parent.down(2).id, + "should not assign duplicate IDs"); }, testElementClassNameMethod: function() { @@ -760,10 +765,12 @@ new Test.Unit.Runner({ }, testDescendantOf: function() { - this.assert($('child').descendantOf('ancestor')); - this.assert($('child').descendantOf($('ancestor'))); - - this.assert(!$('ancestor').descendantOf($('child'))); + this.assert($('child').descendantOf('ancestor'), + '#child should be descendant of #ancestor'); + this.assert($('child').descendantOf($('ancestor')), + '#child should be descendant of #ancestor'); + this.assert(!$('ancestor').descendantOf($('child')), + '#ancestor should not be descendant of child'); this.assert($('great-grand-child').descendantOf('ancestor'), 'great-grand-child < ancestor'); this.assert($('grand-child').descendantOf('ancestor'), 'grand-child < ancestor'); @@ -783,18 +790,23 @@ new Test.Unit.Runner({ this.assert(!$('great-grand-child').descendantOf('not-in-the-family'), 'great-grand-child < not-in-the-family'); this.assert(!$('child').descendantOf('not-in-the-family'), 'child < not-in-the-family'); - this.assert(!$(document.body).descendantOf('great-grand-child')); + this.assert(!$(document.body).descendantOf('great-grand-child'), + 'BODY should not be descendant of anything within it'); // dynamically-created elements $('ancestor').insert(new Element('div', { id: 'weird-uncle' })); - this.assert($('weird-uncle').descendantOf('ancestor')); + this.assert($('weird-uncle').descendantOf('ancestor'), + 'dynamically-created element should work properly'); $(document.body).insert(new Element('div', { id: 'impostor' })); - this.assert(!$('impostor').descendantOf('ancestor')); + this.assert(!$('impostor').descendantOf('ancestor'), + 'elements inserted elsewhere in the DOM tree should not be descendants'); // test descendantOf document - this.assert($(document.body).descendantOf(document)); - this.assert($(document.documentElement).descendantOf(document)); + this.assert($(document.body).descendantOf(document), + 'descendantOf(document) should behave predictably'); + this.assert($(document.documentElement).descendantOf(document), + 'descendantOf(document) should behave predictably'); }, testChildOf: function() { @@ -829,11 +841,9 @@ new Test.Unit.Runner({ $('style_test_3').setStyle({ opacity: 0.5 }); this.assertEqual(0.5, $('style_test_3').getStyle('opacity')); - window.debug = true; $('style_test_3').setStyle({ opacity: '' }); this.assertEqual(1, $('style_test_3').getStyle('opacity'), '#style_test_3 opacity should be 1'); - window.debug = false; $('style_test_3').setStyle({ opacity: 0 }); this.assertEqual(0, $('style_test_3').getStyle('opacity'), @@ -869,7 +879,7 @@ new Test.Unit.Runner({ }, testElementSetOpacity: function() { - [0,0.1,0.5,0.999].each(function(opacity){ + [0, 0.1, 0.5, 0.999].each(function(opacity){ $('style_test_3').setOpacity(opacity); // b/c of rounding issues on IE special case @@ -877,7 +887,10 @@ new Test.Unit.Runner({ // opera rounds off to two significant digits, so we check for a // ballpark figure - this.assert((Number(realOpacity) - opacity) <= 0.002, 'setting opacity to ' + opacity); + this.assert( + (Number(realOpacity) - opacity) <= 0.002, + 'setting opacity to ' + opacity + ' (actual: ' + realOpacity + ')' + ); }, this); this.assertEqual(0, From 660c08a935c33f1ee98b2cf1f6d489197db1de66 Mon Sep 17 00:00:00 2001 From: Pete Deffendol Date: Fri, 10 Dec 2010 10:42:06 -0700 Subject: [PATCH 292/502] Element.absolutize should save existing position style so that relativize restores it. --- src/prototype/dom/layout.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index d55fd87fd..292075845 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1056,10 +1056,11 @@ var layout = element.getLayout(); element.store('prototype_absolutize_original_styles', { - left: element.getStyle('left'), - top: element.getStyle('top'), - width: element.getStyle('width'), - height: element.getStyle('height') + position: element.getStyle('position'), + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') }); element.setStyle({ From 38cafcc1dd6fe2ff172febb8f613e5fed3a7ff62 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 18 Apr 2011 22:12:39 -0500 Subject: [PATCH 293/502] Documentation fixes. --- src/dom/dom.js | 33 ++++++++++++++++++++++++++++----- src/dom/event.js | 14 +++++++++----- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 02f5b80e5..080adf8d4 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -290,9 +290,16 @@ } /** - * Element.toggle(@element) -> Element + * Element.toggle(@element[, bool]) -> Element * - * Toggles the visibility of `element`. Returns `element`. + * Toggles the CSS `display` of `element`. Returns `element`. + * + * Switches an element's CSS `display` between `none` and its inherited + * value (usually `block` or `inline`). + * + * By default, `toggle` will switch the display to the opposite of its + * current state, but will use the `bool` argument instead if it's + * provided (`true` to show the element, `false` to hide it). * * ##### Examples * @@ -305,6 +312,10 @@ * $('error-message').toggle(); * // -> Element (and displays div#error-message) * + * $('error-message).toggle(true); + * // -> Element (and displays div#error-message, no matter what its + * // previous state) + * * Toggle multiple elements using [[Enumerable#each]]: * * ['error-message', 'welcome-message'].each(Element.toggle); @@ -314,6 +325,11 @@ * * $('error-message', 'welcome-message').invoke('toggle'); * // -> [Element, Element] + * + * $('error-message', 'welcome-message').invoke('toggle', false); + * // -> [Element, Element] (and hides both elements, no matter what + * their previous state) + * * * ##### Notes * @@ -331,7 +347,7 @@ * *
    * - * $('hidden-by-css').toggle(); // WONT' WORK! + * $('hidden-by-css').toggle(); // WON'T WORK! * // -> Element (div#hidden-by-css is still hidden!) **/ function toggle(element, bool) { @@ -2485,9 +2501,13 @@ } /** - * Element.toggleClassName(@element, className) -> Element + * Element.toggleClassName(@element, className[, bool]) -> Element * * Toggles the presence of CSS class `className` on `element`. + * + * By default, `toggleClassName` will flip to the opposite state, but + * will use `bool` instead if it's given; `true` will add the class name + * and `false` will remove it. * * ##### Examples * @@ -2500,10 +2520,13 @@ * // -> false * * $('mutsu').toggleClassName('fruit'); - * // -> element + * // -> Element * * $('mutsu').hasClassName('fruit'); * // -> true + * + * $('mutsu').toggleClassName('fruit', true); + * // -> Element (keeps the "fruit" class name that was already there) **/ function toggleClassName(element, className, bool) { if (!(element = $(element))) return; diff --git a/src/dom/event.js b/src/dom/event.js index 75b418bde..b086b8cb0 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -99,7 +99,7 @@ // (Though Opera supports both systems, the event object appears to be // the same no matter which system is used. That means that this function // will always return `true` in Opera, but that's OK; it keeps us from - // having to do a browser sniff. + // having to do a browser sniff.) isIELegacyEvent = function(event) { return !(event instanceof window.Event); }; @@ -151,6 +151,7 @@ /** * Event.isLeftClick(@event) -> Boolean + * - event (Event): An Event object * * Determines whether a button-related mouse event involved the left * mouse button. @@ -163,6 +164,7 @@ /** * Event.isMiddleClick(@event) -> Boolean + * - event (Event): An Event object * * Determines whether a button-related mouse event involved the middle * mouse button. @@ -171,11 +173,12 @@ /** * Event.isRightClick(@event) -> Boolean + * - event (Event): An Event object * * Determines whether a button-related mouse event involved the right * mouse button. * - * Keep in mind that the "left" mouse button is actually the "secondary" + * Keep in mind that the "right" mouse button is actually the "secondary" * mouse button. When a mouse is in left-handed mode, the browser will * report clicks of the _left_ button as "left-clicks." **/ @@ -255,11 +258,11 @@ * * ##### Example * - * Here's a simple code that lets you click everywhere on the page and hides - * the closest-fitting paragraph around your click (if any). + * Here's a simple example that lets you click everywhere on the page and + * hides the closest-fitting paragraph around your click (if any). * * document.observe('click', function(event) { - * var element = Event.findElement(event, 'p'); + * var element = event.findElement('p'); * if (element != document) * $(element).hide(); * }); @@ -433,6 +436,7 @@ /** * Event.extend(@event) -> Event + * - event (Event): An Event object * * Extends `event` with all of the methods contained in `Event.Methods`. * From 24d94990046930c8ddac2ad3c6ec8099b205a4f3 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 3 Jul 2011 21:14:15 -0500 Subject: [PATCH 294/502] Fix erroneous early return. --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 080adf8d4..a068c2909 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3359,7 +3359,7 @@ * failure handler (since we didn't supply one). **/ function addMethods(methods) { - if (arguments.length === 0) return addFormMethods(); + if (arguments.length === 0) addFormMethods(); if (arguments.length === 2) { // Tag names have been specified. From 433895ece475d1ac8ef500546291cb39274aa0d8 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 3 Jul 2011 21:22:30 -0500 Subject: [PATCH 295/502] Update the URL for the Sizzle submodule. --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index af46232a5..1ec074308 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,7 +17,7 @@ url = git://github.com/dperini/nwmatcher.git [submodule "vendor/sizzle/repository"] path = vendor/sizzle/repository - url = git://github.com/jeresig/sizzle.git + url = git://github.com/jquery/sizzle.git [submodule "vendor/slick/repository"] path = vendor/slick/repository url = git://github.com/mootools/slick.git From f9d7c6797804c396d52f4f20ce44a060c6d69fb3 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 3 Jul 2011 21:23:06 -0500 Subject: [PATCH 296/502] Update to latest revision of Sizzle. --- vendor/sizzle/repository | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sizzle/repository b/vendor/sizzle/repository index 415e466f7..3ba396e43 160000 --- a/vendor/sizzle/repository +++ b/vendor/sizzle/repository @@ -1 +1 @@ -Subproject commit 415e466f70e5a53f589161b1f2944e5485007409 +Subproject commit 3ba396e439a07c2a2facafbe07cdaa1b80a24c00 From 2a6914089ad6f0b398d9f4ef7032e6bab818dd51 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 3 Jul 2011 21:23:29 -0500 Subject: [PATCH 297/502] Update built-in version of Sizzle to latest revision. --- src/sizzle.js | 962 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 680 insertions(+), 282 deletions(-) diff --git a/src/sizzle.js b/src/sizzle.js index 801b73196..07654d594 100644 --- a/src/sizzle.js +++ b/src/sizzle.js @@ -1,29 +1,33 @@ /*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, hasDuplicate = false, - baseHasDuplicate = true; + baseHasDuplicate = true, + rBackslash = /\\/g, + rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. -[0, 0].sort(function(){ +[0, 0].sort(function() { baseHasDuplicate = false; return 0; }); -var Sizzle = function(selector, context, results, seed) { +var Sizzle = function( selector, context, results, seed ) { results = results || []; - var origContext = context = context || document; + context = context || document; + + var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; @@ -33,24 +37,34 @@ var Sizzle = function(selector, context, results, seed) { return results; } - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], soFar = selector; // Reset the position of the chunker regexp (start from head) - while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { - soFar = m[3]; + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; - parts.push( m[1] ); + parts.push( m[1] ); - if ( m[2] ) { - extra = m[3]; - break; + if ( m[2] ) { + extra = m[3]; + break; + } } - } + } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); + } else { set = Expr.relative[ parts[0] ] ? [ context ] : @@ -59,35 +73,45 @@ var Sizzle = function(selector, context, results, seed) { while ( parts.length ) { selector = parts.shift(); - if ( Expr.relative[ selector ] ) + if ( Expr.relative[ selector ] ) { selector += parts.shift(); - + } + set = posProcess( selector, set ); } } + } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; } if ( context ) { - var ret = seed ? + ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; if ( parts.length > 0 ) { - checkSet = makeArray(set); + checkSet = makeArray( set ); + } else { prune = false; } while ( parts.length ) { - var cur = parts.pop(), pop = cur; + cur = parts.pop(); + pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; @@ -101,6 +125,7 @@ var Sizzle = function(selector, context, results, seed) { Expr.relative[ cur ]( checkSet, pop, contextXML ); } + } else { checkSet = parts = []; } @@ -111,25 +136,28 @@ var Sizzle = function(selector, context, results, seed) { } if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); + Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } + } else { - for ( var i = 0; checkSet[i] != null; i++ ) { + for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } + } else { makeArray( checkSet, results ); } @@ -142,15 +170,15 @@ var Sizzle = function(selector, context, results, seed) { return results; }; -Sizzle.uniqueSort = function(results){ +Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); + results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); } } } @@ -159,27 +187,33 @@ Sizzle.uniqueSort = function(results){ return results; }; -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; }; -Sizzle.find = function(expr, context, isXML){ - var set, match; +Sizzle.find = function( expr, context, isXML ) { + var set; if ( !expr ) { return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; + var match, + type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; - match.splice(1,1); + match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); + match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; @@ -189,23 +223,37 @@ Sizzle.find = function(expr, context, isXML){ } if ( !set ) { - set = context.getElementsByTagName("*"); + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; } - return {set: set, expr: expr}; + return { set: set, expr: expr }; }; -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + anyFound = false; - if ( curLoop == result ) { + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { result = []; } @@ -214,6 +262,7 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( !match ) { anyFound = found = true; + } else if ( match === true ) { continue; } @@ -228,9 +277,11 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( inplace && found != null ) { if ( pass ) { anyFound = true; + } else { curLoop[i] = false; } + } else if ( pass ) { result.push( item ); anyFound = true; @@ -256,9 +307,10 @@ Sizzle.filter = function(expr, set, inplace, not){ } // Improper expression - if ( expr == old ) { + if ( expr === old ) { if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; + Sizzle.error( expr ); + } else { break; } @@ -270,43 +322,55 @@ Sizzle.filter = function(expr, set, inplace, not){ return curLoop; }; +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], + match: { - ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, + leftMatch: {}, + attrMap: { "class": "className", "for": "htmlFor" }, + attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); } }, + relative: { - "+": function(checkSet, part, isXML){ + "+": function(checkSet, part){ var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), + isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; - if ( isTag && !isXML ) { - part = part.toUpperCase(); + if ( isTag ) { + part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } @@ -316,22 +380,29 @@ var Expr = Sizzle.selectors = { Sizzle.filter( part, checkSet, true ); } }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; - if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } + } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : @@ -344,37 +415,50 @@ var Expr = Sizzle.selectors = { } } }, + "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; + var nodeCheck, + doneName = done++, + checkFn = dirCheck; - if ( !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; checkFn = dirNodeCheck; } - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; checkFn = dirNodeCheck; } - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, + find: { - ID: function(match, context, isXML){ + ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); - return m ? [m] : []; + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; } }, - NAME: function(match, context, isXML){ + + NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); + var ret = [], + results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { @@ -385,13 +469,16 @@ var Expr = Sizzle.selectors = { return ret.length === 0 ? null : ret; } }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } } }, preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; @@ -399,9 +486,11 @@ var Expr = Sizzle.selectors = { for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { result.push( elem ); + } + } else if ( inplace ) { curLoop[i] = false; } @@ -410,190 +499,271 @@ var Expr = Sizzle.selectors = { return false; }, - ID: function(match){ - return match[1].replace(/\\/g, ""); + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); }, - TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); }, - CHILD: function(match){ - if ( match[1] == "nth" ) { + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } // TODO: Move to normal caching system match[0] = done++; return match; }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, - PSEUDO: function(match, curLoop, inplace, result, not){ + + PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); + } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { result.push.apply( result, ret ); } + return false; } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, - POS: function(match){ + + POS: function( match ) { match.unshift( true ); + return match; } }, + filters: { - enabled: function(elem){ + enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, - disabled: function(elem){ + + disabled: function( elem ) { return elem.disabled === true; }, - checked: function(elem){ + + checked: function( elem ) { return elem.checked === true; }, - selected: function(elem){ + + selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly - elem.parentNode.selectedIndex; + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + return elem.selected === true; }, - parent: function(elem){ + + parent: function( elem ) { return !!elem.firstChild; }, - empty: function(elem){ + + empty: function( elem ) { return !elem.firstChild; }, - has: function(elem, i, match){ + + has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); }, - text: function(elem){ - return "text" === elem.type; + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, - radio: function(elem){ - return "radio" === elem.type; + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, - checkbox: function(elem){ - return "checkbox" === elem.type; + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, - file: function(elem){ - return "file" === elem.type; + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, - password: function(elem){ - return "password" === elem.type; + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; }, - submit: function(elem){ - return "submit" === elem.type; + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, - image: function(elem){ - return "image" === elem.type; + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; }, - reset: function(elem){ - return "reset" === elem.type; + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; } }, setFilters: { - first: function(elem, i){ + first: function( elem, i ) { return i === 0; }, - last: function(elem, i, match, array){ + + last: function( elem, i, match, array ) { return i === array.length - 1; }, - even: function(elem, i){ + + even: function( elem, i ) { return i % 2 === 0; }, - odd: function(elem, i){ + + odd: function( elem, i ) { return i % 2 === 1; }, - lt: function(elem, i, match){ + + lt: function( elem, i, match ) { return i < match[3] - 0; }, - gt: function(elem, i, match){ + + gt: function( elem, i, match ) { return i > match[3] - 0; }, - nth: function(elem, i, match){ - return match[3] - 0 == i; + + nth: function( elem, i, match ) { + return match[3] - 0 === i; }, - eq: function(elem, i, match){ - return match[3] - 0 == i; + + eq: function( elem, i, match ) { + return match[3] - 0 === i; } }, filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); + } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { var not = match[3]; - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { return false; } } return true; + + } else { + Sizzle.error( name ); } }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) return false; + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } } - if ( type == 'first') return true; + + if ( type === "first" ) { + return true; + } + node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) return false; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } } + return true; - case 'nth': - var first = match[2], last = match[3]; - if ( first == 1 && last == 0 ) { + case "nth": + var first = match[2], + last = match[3]; + + if ( first === 1 && last === 0 ) { return true; } @@ -602,33 +772,41 @@ var Expr = Sizzle.selectors = { if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } + parent.sizcache = doneName; } var diff = elem.nodeIndex - last; - if ( first == 0 ) { - return diff == 0; + + if ( first === 0 ) { + return diff === 0; + } else { - return ( diff % first == 0 && diff / first >= 0 ); + return ( diff % first === 0 && diff / first >= 0 ); } } }, - ID: function(elem, match){ + + ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, - CLASS: function(elem, match){ + + CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, - ATTR: function(elem, match){ + + ATTR: function( elem, match ) { var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : @@ -650,7 +828,7 @@ var Expr = Sizzle.selectors = { !check ? value && result !== false : type === "!=" ? - value != check : + value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? @@ -659,8 +837,10 @@ var Expr = Sizzle.selectors = { value === check || value.substr(0, check.length + 1) === check + "-" : false; }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); @@ -669,14 +849,17 @@ var Expr = Sizzle.selectors = { } }; -var origPOS = Expr.match.POS; +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } -var makeArray = function(array, results) { +var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { @@ -689,23 +872,28 @@ var makeArray = function(array, results) { // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); + } else { if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { + for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } + } else { - for ( var i = 0; array[i]; i++ ) { + for ( ; array[i]; i++ ) { ret.push( array[i] ); } } @@ -715,90 +903,161 @@ try { }; } -var sortOrder; +var sortOrder, siblingCheck; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - if ( a == b ) { - hasDuplicate = true; - } + if ( a === b ) { + hasDuplicate = true; return 0; } - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; } - return ret; + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; -} else if ( "sourceIndex" in document.documentElement ) { + +} else { sortOrder = function( a, b ) { - if ( !a.sourceIndex || !b.sourceIndex ) { - if ( a == b ) { - hasDuplicate = true; - } + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; } - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - if ( !a.ownerDocument || !b.ownerDocument ) { - if ( a == b ) { - hasDuplicate = true; + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); } - return 0; } - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; } - return ret; + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; }; } +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), - id = "script" + (new Date).getTime(); + id = "script" + (new Date()).getTime(), + root = document.documentElement; + form.innerHTML = ""; // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) - if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; } }; - Expr.filter.ID = function(elem, match){ + Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); - root = form = null; // release memory in IE + + // release memory in IE + root = form = null; })(); (function(){ @@ -811,8 +1070,8 @@ if ( document.documentElement.compareDocumentPosition ) { // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { @@ -833,83 +1092,209 @@ if ( document.documentElement.compareDocumentPosition ) { // Check to see if an attribute returns normalized href attributes div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); }; } - div = null; // release memory in IE + // release memory in IE + div = null; })(); -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; + div.innerHTML = "

    "; - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } - return oldSizzle(query, context, extra, seed); - }; + return oldSizzle(query, context, extra, seed); + }; - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} - div = null; // release memory in IE +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } })(); -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ +(function(){ var div = document.createElement("div"); + div.innerHTML = "
    "; // Opera can't find a second classname (in 9.6) - if ( div.getElementsByClassName("e").length === 0 ) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; + } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; - if ( div.getElementsByClassName("e").length === 1 ) + if ( div.getElementsByClassName("e").length === 1 ) { return; - + } + Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { + Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; - div = null; // release memory in IE + // release memory in IE + div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; var match = false; + elem = elem[dir]; + while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; @@ -921,7 +1306,7 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem.sizset = i; } - if ( elem.nodeName === cur ) { + if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } @@ -935,16 +1320,13 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; var match = false; + + elem = elem[dir]; while ( elem ) { if ( elem.sizcache === doneName ) { @@ -957,6 +1339,7 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem.sizcache = doneName; elem.sizset = i; } + if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; @@ -977,19 +1360,34 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } } -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; + return documentElement ? documentElement.nodeName !== "HTML" : false; }; -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter From f1a5e42aa91f3b9af5c85f884a9845b158921b42 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 19 Oct 2011 18:37:52 -0500 Subject: [PATCH 298/502] Fix bug in isSimulatedMouseEnterLeaveEvent. [#1269 state:resolved] (Victor, Andrew Dupont) --- src/prototype/dom/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index b086b8cb0..86dbfcf8a 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -1297,7 +1297,7 @@ && 'onmouseleave' in docEl; function isSimulatedMouseEnterLeaveEvent(eventName) { - return MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + return !MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === 'mouseenter' || eventName === 'mouseleave'); } From 45fe222262a34f3cd2991fd09bf4f0739801fc3a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 19 Oct 2011 18:50:25 -0500 Subject: [PATCH 299/502] Optimize `Form.getElements`. [#1147 state:resolved] (Victor, Andrew Dupont) --- src/prototype/dom/form.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/prototype/dom/form.js b/src/prototype/dom/form.js index 8088e9d3d..ee3efb2b1 100644 --- a/src/prototype/dom/form.js +++ b/src/prototype/dom/form.js @@ -184,21 +184,16 @@ Form.Methods = { * OPTION elements are not included in the result; only their parent * SELECT control is. **/ + getElements: function(form) { - var elements = $(form).getElementsByTagName('*'), - element, - arr = [ ], - serializers = Form.Element.Serializers; - // `length` is not used to prevent interference with - // length-named elements shadowing `length` of a nodelist + var elements = $(form).getElementsByTagName('*'); + var element, results = [], serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { - arr.push(element); + if (serializers[element.tagName.toLowerCase()]) + results.push(Element.extend(element)); } - return arr.inject([], function(elements, child) { - if (serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - }) + return results; }, /** From a5dec720ca797b70b3d5699085fe0e9fee821993 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 19 Oct 2011 18:59:02 -0500 Subject: [PATCH 300/502] Ensure `makeClipping` and `undoClipping` properly restore an "auto" value. [#1063 state:resolved] (Victor, Andrew Dupont) --- src/prototype/dom/layout.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index 71027f60a..b64481f2f 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1285,8 +1285,11 @@ var storage = Element.getStorage(element), madeClipping = storage.get('prototype_made_clipping'); - if (!madeClipping) { - var overflow = Element.getStyle(element, 'overflow') || 'auto'; + // The "prototype_made_clipping" storage key is meant to hold the + // original CSS overflow value. A string value or `null` means that we've + // called `makeClipping` already. An `undefined` value means we haven't. + if (Object.isUndefined(madeClipping)) { + var overflow = Element.getStyle(element, 'overflow'); storage.set('prototype_made_clipping', overflow); if (overflow !== 'hidden') element.style.overflow = 'hidden'; @@ -1347,12 +1350,12 @@ var storage = Element.getStorage(element), overflow = storage.get('prototype_made_clipping'); - if (overflow) { + if (!Object.isUndefined(overflow)) { storage.unset('prototype_made_clipping'); - element.style.overflow = (overflow === 'auto') ? '' : overflow; + element.style.overflow = overflow || ''; } - return element; + return element; } /** From 54caddaf43cd4422eda10f7f9a521ca521809a1c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 19 Oct 2011 19:02:13 -0500 Subject: [PATCH 301/502] Fix documentation typo. [#1051 state:resolved] --- src/prototype/dom/dom.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index a068c2909..640f88641 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1347,14 +1347,14 @@ * Then: * * $('australopithecus').firstDescendant(); - * // -> div#homo-herectus + * // -> div#homo-erectus * * // the DOM property returns any first node - * $('homo-herectus').firstChild; + * $('homo-erectus').firstChild; * // -> comment node "Latin is super" * * // this is what we want! - * $('homo-herectus').firstDescendant(); + * $('homo-erectus').firstDescendant(); * // -> div#homo-neanderthalensis **/ function firstDescendant(element) { From 075625c037f1201a227c6ee2ab2b628c25bf8cb1 Mon Sep 17 00:00:00 2001 From: rlineweaver Date: Mon, 31 Oct 2011 09:58:48 -0400 Subject: [PATCH 302/502] avoiding infinite loop in String#gsub when there is a zero length match --- src/prototype/lang/string.js | 3 ++- test/unit/string_test.js | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 7b58b5c3e..f2ccd0b07 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -100,7 +100,8 @@ Object.extend(String.prototype, (function() { } while (source.length > 0) { - if (match = source.match(pattern)) { + match = source.match(pattern) + if (match && match[0].length > 0) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 2851950f3..bec0ebaf7 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -66,7 +66,6 @@ new Test.Unit.Runner({ testGsubWithTroublesomeCharacters: function() { this.assertEqual('ab', 'a|b'.gsub('|', '')); - //'ab'.gsub('', ''); // freeze this.assertEqual('ab', 'ab(?:)'.gsub('(?:)', '')); this.assertEqual('ab', 'ab()'.gsub('()', '')); this.assertEqual('ab', 'ab'.gsub('^', '')); @@ -77,6 +76,12 @@ new Test.Unit.Runner({ this.assertEqual('ab', 'a.b'.gsub('.', '')); }, + testGsubWithZeroLengthMatch: function() { + this.assertEqual('ab', 'ab'.gsub('', '')); + this.assertEqual('a', 'a'.gsub(/b*/, 'c')); + this.assertEqual('abc', 'abc'.gsub(/b{0}/, '')); + }, + testSubWithReplacementFunction: function() { var source = 'foo boo boz'; From 38c8fe9af7061d2cb86f427019786d1e9e5e04dd Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 29 Nov 2011 22:07:50 +0400 Subject: [PATCH 303/502] typos fixed --- src/prototype/dom/event.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index b086b8cb0..a3ba56c0c 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -1132,6 +1132,7 @@ Element.addMethods({ /** * Element.fire(@element, eventName[, memo[, bubble = true]]) -> Event + * * See [[Event.fire]]. * * Fires a custom event with the current element as its target. @@ -1177,18 +1178,21 @@ /** * Element.observe(@element, eventName, handler) -> Element + * * See [[Event.observe]]. **/ observe: observe, /** * Element.stopObserving(@element[, eventName[, handler]]) -> Element + * * See [[Event.stopObserving]]. **/ stopObserving: stopObserving, /** * Element.on(@element, eventName[, selector], callback) -> Element + * * See [[Event.on]]. **/ on: on From 377d034fc73f7faa09d502dbe7be551cb39c4293 Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 29 Nov 2011 22:10:50 +0400 Subject: [PATCH 304/502] typo fixed --- src/prototype/dom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prototype/dom.js b/src/prototype/dom.js index 6003cd048..a93263edb 100644 --- a/src/prototype/dom.js +++ b/src/prototype/dom.js @@ -8,6 +8,7 @@ /** * == DOM == + * * Extensions to DOM elements, plus other utilities for DOM traversal * and modification. * From 3ad26a54c5b93b36ab7b9235452da3a30855ffe0 Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 29 Nov 2011 22:12:09 +0400 Subject: [PATCH 305/502] typos fixed --- src/prototype/lang/range.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/prototype/lang/range.js b/src/prototype/lang/range.js index 208637df6..501049857 100644 --- a/src/prototype/lang/range.js +++ b/src/prototype/lang/range.js @@ -94,11 +94,13 @@ var ObjectRange = Class.create(Enumerable, (function() { function initialize(start, end, exclusive) { /** * ObjectRange#start -> ? + * * The lower bounding value of the range. **/ this.start = start; /** * ObjectRange#end -> ? + * * The upper bounding value of the range. **/ this.end = end; From 84ff6f341bf5c67824cca7672ff362ddadc0e2cd Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 29 Nov 2011 22:12:56 +0400 Subject: [PATCH 306/502] Update src/prototype/lang.js --- src/prototype/lang.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prototype/lang.js b/src/prototype/lang.js index 8a23edfb7..ff236d156 100644 --- a/src/prototype/lang.js +++ b/src/prototype/lang.js @@ -15,6 +15,7 @@ /** * == Language == + * * Additions to JavaScript's "standard library" and extensions to * built-in JavaScript objects. **/ From 768a15033849c74df96ddd5e8837992ca6783ae5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:36:00 -0600 Subject: [PATCH 307/502] A few fixes for layout.js: * Use an internal function for getting styles instead of the public `Element.getStyle`. * Fix measurement of hidden `position: fixed` elements in IE. * Add comments for clarity. --- src/prototype/dom/layout.js | 157 +++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 38 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index b64481f2f..90dd70a9c 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -9,6 +9,58 @@ return (Number(match[1]) / 100); } + // A bare-bones version of Element.getStyle. Needed because getStyle is + // public-facing and too user-friendly for our tastes. We need raw, + // non-normalized values. + // + // Camel-cased property names only. + function getRawStyle(element, style) { + element = $(element); + + // Try inline styles first. + var value = element.style[style]; + if (!value || value === 'auto') { + // Reluctantly retrieve the computed style. + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + + if (style === 'opacity') return value ? parseFloat(value) : 1.0; + return value === 'auto' ? null : value; + } + + function getRawStyle_IE(element, style) { + // Try inline styles first. + var value = element.style[style]; + if (!value && element.currentStyle) { + // Reluctantly retrieve the current style. + value = element.currentStyle[style]; + } + return value; + } + + // Quickly figures out the content width of an element. Used instead of + // `element.measure('width')` in several places below; we don't want to + // call back into layout code recursively if we don't have to. + // + // But this means it doesn't handle edge cases. Use it when you know the + // element in question is visible and will give accurate measurements. + function getContentWidth(element, context) { + var boxWidth = element.offsetWidth; + + var bl = getPixelValue(element, 'borderLeftWidth', context) || 0; + var br = getPixelValue(element, 'borderRightWidth', context) || 0; + var pl = getPixelValue(element, 'paddingLeft', context) || 0; + var pr = getPixelValue(element, 'paddingRight', context) || 0; + + return boxWidth - bl - br - pl - pr; + } + + if ('currentStyle' in document.documentElement) { + getRawStyle = getRawStyle_IE; + } + + // Can be called like this: // getPixelValue("11px"); // Or like this: @@ -17,10 +69,10 @@ var element = null; if (Object.isElement(value)) { element = value; - value = element.getStyle(property); + value = getRawStyle(element, property); } - if (value === null) { + if (value === null || Object.isUndefined(value)) { return null; } @@ -30,8 +82,8 @@ if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { return window.parseFloat(value); } - - var isPercentage = value.include('%'), isViewport = (context === document.viewport); + + var isPercentage = value.include('%'), isViewport = (context === document.viewport); // When IE gives us something other than a pixel value, this technique // (invented by Dean Edwards) will convert it to pixels. @@ -48,11 +100,16 @@ return value; } - + // For other browsers, we have to do a bit of work. // (At this point, only percentages should be left; all other CSS units // are converted to pixels by getComputedStyle.) if (element && isPercentage) { + // The `context` argument comes into play for percentage units; it's + // the thing that the unit represents a percentage of. When an + // absolutely-positioned element has a width of 50%, we know that's + // 50% of its offset parent. If it's `position: fixed` instead, we know + // it's 50% of the viewport. And so on. context = context || element.parentNode; var decimal = toDecimal(value); var whole = null; @@ -61,7 +118,7 @@ var isHorizontal = property.include('left') || property.include('right') || property.include('width'); - var isVertical = property.include('top') || property.include('bottom') || + var isVertical = property.include('top') || property.include('bottom') || property.include('height'); if (context === document.viewport) { @@ -87,12 +144,12 @@ // Turns plain numbers into pixel measurements. function toCSSPixels(number) { - if (Object.isString(number) && number.endsWith('px')) { + if (Object.isString(number) && number.endsWith('px')) return number; - } return number + 'px'; } + // Shortcut for figuring out if an element is `display: none` or not. function isDisplayed(element) { var originalElement = element; while (element && element.parentNode) { @@ -105,6 +162,8 @@ return true; } + // In IE6-7, positioned elements often need hasLayout triggered before they + // report accurate measurements. var hasLayout = Prototype.K; if ('currentStyle' in document.documentElement) { hasLayout = function(element) { @@ -282,7 +341,7 @@ **/ get: function($super, property) { // Try to fetch from the cache. - var value = $super(property); + var value = $super(property); return value === null ? this._compute(property) : value; }, @@ -291,14 +350,24 @@ // when hidden), elements need a "preparation" phase that ensures // accuracy of measurements. _begin: function() { - if (this._prepared) return; - + if (this._isPrepared()) return; + var element = this.element; if (isDisplayed(element)) { - this._prepared = true; + this._setPrepared(true); return; } + // If we get this far, it means this element is hidden. To get usable + // measurements, we must remove `display: none`, but in a manner that + // isn't noticeable to the user. That means we also set + // `visibility: hidden` to make it invisible, and `position: absolute` + // so that it won't alter the document flow when displayed. + // + // Once we do this, the element is "prepared," and we can make our + // measurements. When we're done, the `_end` method cleans up our + // changes. + // Remember the original values for some styles we're going to alter. var originalStyles = { position: element.style.position || '', @@ -307,33 +376,36 @@ display: element.style.display || '' }; - // We store them so that the `_end` function can retrieve them later. + // We store them so that the `_end` method can retrieve them later. element.store('prototype_original_styles', originalStyles); - var position = element.getStyle('position'), - width = element.getStyle('width'); - - if (width === "0px" || width === null) { - // Opera won't report the true width of the element through + var position = getRawStyle(element, 'position'), width = element.offsetWidth; + + if (width === 0 || width === null) { + // Opera/IE won't report the true width of the element through // `getComputedStyle` if it's hidden. If we got a nonsensical value, // we need to show the element and try again. element.style.display = 'block'; - width = element.getStyle('width'); + width = element.offsetWidth; } // Preserve the context in case we get a percentage value. var context = (position === 'fixed') ? document.viewport : element.parentNode; - element.setStyle({ - position: 'absolute', + var tempStyles = { visibility: 'hidden', display: 'block' - }); + }; - var positionedWidth = element.getStyle('width'); + // If the element's `position: fixed`, it's already out of the document + // flow, so it's both unnecessary and inaccurate to set + // `position: absolute`. + if (position !== 'fixed') tempStyles.position = 'absolute'; + + element.setStyle(tempStyles); - var newWidth; + var positionedWidth = element.offsetWidth, newWidth; if (width && (positionedWidth === width)) { // If the element's width is the same both before and after // we set absolute positioning, that means: @@ -341,11 +413,11 @@ // (b) it has an explicitly-set width, instead of width: auto. // Either way, it means the element is the width it needs to be // in order to report an accurate height. - newWidth = getPixelValue(element, 'width', context); + newWidth = getContentWidth(element, context); } else if (position === 'absolute' || position === 'fixed') { // Absolute- and fixed-position elements' dimensions don't depend // upon those of their parents. - newWidth = getPixelValue(element, 'width', context); + newWidth = getContentWidth(element, context); } else { // Otherwise, the element's width depends upon the width of its // parent. @@ -360,18 +432,20 @@ this.get('margin-right'); } + // Whatever the case, we've now figured out the correct `width` value + // for the element. element.setStyle({ width: newWidth + 'px' }); // The element is now ready for measuring. - this._prepared = true; + this._setPrepared(true); }, _end: function() { var element = this.element; var originalStyles = element.retrieve('prototype_original_styles'); - element.store('prototype_original_styles', null); + element.store('prototype_original_styles', null); element.setStyle(originalStyles); - this._prepared = false; + this._setPrepared(false); }, _compute: function(property) { @@ -383,6 +457,14 @@ return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, + _isPrepared: function() { + return this.element.retrieve('prototype_element_layout_prepared', false); + }, + + _setPrepared: function(bool) { + return this.element.store('prototype_element_layout_prepared', bool); + }, + /** * Element.Layout#toObject([keys...]) -> Object * - keys (String): A space-separated list of keys to include. @@ -459,8 +541,6 @@ if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; var value = this.get(key); - // Unless the value is null, add 'px' to the end and add it to the - // returned object. if (value != null) css[cssNameFor(key)] = value + 'px'; }, this); return css; @@ -523,9 +603,8 @@ var pLeft = this.get('padding-left'), pRight = this.get('padding-right'); - + if (!this._preComputing) this._end(); - return bWidth - bLeft - bRight - pLeft - pRight; }, @@ -766,10 +845,11 @@ * `Element.getLayout` again only when the values in an existing * `Element.Layout` object have become outdated. * - * Remember that instances of `Element.Layout` compute values the first - * time they're asked for and remember those values for later retrieval. - * If you want to compute all an element's measurements at once, pass - * + * If the `preCompute` argument is `true`, all properties will be measured + * when the layout object is instantiated. If you plan to measure several + * properties of an element's dimensions, it's probably worth it to get a + * pre-computed hash. + * * ##### Examples * * var layout = $('troz').getLayout(); @@ -1512,8 +1592,9 @@ do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) + if (element.offsetParent == document.body) { if (Element.getStyle(element, 'position') == 'absolute') break; + } element = element.offsetParent; } while (element); From 531e4ea1d65f54d2bf3e06ad7ca2078aaf333fea Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:36:38 -0600 Subject: [PATCH 308/502] Remove unnecessary `void 0`. --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 640f88641..cab5c67ad 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1,6 +1,6 @@ (function(GLOBAL) { - var UNDEFINED = void 0; + var UNDEFINED; var SLICE = Array.prototype.slice; // Try to reuse the same created element as much as possible. We'll use From 0d23b66795274931829f9e01984d0b17bfb1673c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:36:57 -0600 Subject: [PATCH 309/502] Fix documentation for dom.js. --- src/prototype/dom/dom.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index cab5c67ad..b5e6a9e54 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -7,6 +7,10 @@ // this DIV for capability checks (where possible) and for normalizing // HTML content. var DIV = document.createElement('div'); + + /** section: DOM + * class Element + **/ /** section: DOM, related to: Element * $(id) -> Element From ce3d1ab179960be9a7009c43520076c4199c0a4b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:39:27 -0600 Subject: [PATCH 310/502] Fix bug with `purgeElement` in dom.js. --- src/prototype/dom/dom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index b5e6a9e54..09814b00f 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1166,7 +1166,7 @@ var uid = getUniqueElementID(element); if (uid) { Element.stopObserving(element); - if (HAS_UNIQUE_ID_PROPERTY) + if (!HAS_UNIQUE_ID_PROPERTY) element._prototypeUID = UNDEFINED; delete Element.Storage[uid]; } @@ -1188,7 +1188,7 @@ } } - if (!window.addEventListener && window.attachEvent) { + if (HAS_UNIQUE_ID_PROPERTY) { purgeCollection = purgeCollection_IE; } From d457b7eb666e5bed687bfbc73a12bcfca7eb9972 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:40:46 -0600 Subject: [PATCH 311/502] Add documentation for dom.js. --- src/prototype/dom/dom.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 09814b00f..d857a750c 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2813,8 +2813,12 @@ * * ##### Notes * - * Internet Explorer returns literal values while other browsers return - * computed values. + * Not all CSS shorthand properties are supported. You may only use the CSS + * properties described in the + * [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). + * + * Old versions of Internet Explorer return _literal_ values; other browsers + * return _computed_ values. * * Consider the following HTML snippet: * @@ -2833,12 +2837,33 @@ * // -> '1em' in Internet Explorer, * // -> '12px' elsewhere. * + * * Safari returns `null` for *any* non-inline property if the element is * hidden (has `display` set to `'none'`). + * + * ##### Caveats + * + * Early versions of Prototype attempted to "fix" this behavior for + * certain properties. A few examples: + * + * 1. Reading and writing the CSS `opacity` property works exactly like + * calling [[Element.getOpacity]] and [[Element.setOpacity]] + * respectively. This lets us pretend that IE didn't have a + * properietary way to set opacity in versions 6-7. + * 2. Browsers disagree on how to report certain properties of hidden + * elements (i.e., `display: none`). Opera, for instance, says that a + * hidden element has a `width` of `0px`. It's an arguable point, but + * we return `null` in those cases instead (so as to agree with the + * majority behavior). **In short: if an element is hidden, + * `getStyle('width')` and `getStyle('height')` will return `null`.** + * 3. In older versions of Internet Explorer, Prototype will return a + * pixel value for `width` and `height`, even if the literal value is + * a different unit. It does this by treating `width` like `offsetWidth` + * and `height` like `offsetHeight`. This is often the incorrect + * measurement, but it's a mistake we're stuck with for + * backward-compatibility. **If you're trying to measure an element's + * dimensions, don't use `getStyle`; use [[Element.measure]] instead.** * - * Not all CSS shorthand properties are supported. You may only use the CSS - * properties described in the - * [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). **/ function getStyle(element, style) { element = $(element); From 701fc701acd2b43b4e8a305754822ea4903e5275 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:41:27 -0600 Subject: [PATCH 312/502] Fix issues causing failing tests in dom.js, mostly related to the `preservingBrowserDimensions` helper function. --- test/unit/dom_test.js | 74 ++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 738c5431b..a8c56f52f 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1448,15 +1448,31 @@ new Test.Unit.Runner({ }, testViewportDimensions: function() { - preservingBrowserDimensions(function() { - window.resizeTo(800, 600); + var original = document.viewport.getDimensions(); + + window.resizeTo(800, 600); + + this.wait(1000, function() { var before = document.viewport.getDimensions(); + + var delta = { width: 800 - before.width, height: 600 - before.height }; + window.resizeBy(50, 50); - var after = document.viewport.getDimensions(); - - this.assertEqual(before.width + 50, after.width, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); - this.assertEqual(before.height + 50, after.height, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); - }.bind(this)); + this.wait(1000, function() { + var after = document.viewport.getDimensions(); + + this.assertEqual(before.width + 50, after.width, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); + this.assertEqual(before.height + 50, after.height, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); + + this.wait(1000, function() { + // Restore original dimensions. + window.resizeTo( + original.width + delta.width, + original.height + delta.height + ); + }); + }) + }); }, testElementToViewportDimensionsDoesNotAffectDocumentProperties: function() { @@ -1475,19 +1491,31 @@ new Test.Unit.Runner({ }, testViewportScrollOffsets: function() { - preservingBrowserDimensions(function() { - window.scrollTo(0, 0); - this.assertEqual(0, document.viewport.getScrollOffsets().top); + var original = document.viewport.getDimensions(); + + window.scrollTo(0, 0); + this.assertEqual(0, document.viewport.getScrollOffsets().top); + + window.scrollTo(0, 35); + this.assertEqual(35, document.viewport.getScrollOffsets().top); - window.scrollTo(0, 35); - this.assertEqual(35, document.viewport.getScrollOffsets().top); + window.resizeTo(200, 650); - window.resizeTo(200, 650); + this.wait(1000, function() { + var before = document.viewport.getDimensions(); + var delta = { width: 200 - before.width, height: 650 - before.height }; + window.scrollTo(25, 35); this.assertEqual(25, document.viewport.getScrollOffsets().left, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THESE TESTS TO PASS"); - - window.resizeTo(850, 650); - }.bind(this)); + + this.wait(1000, function() { + // Restore original dimensions. + window.resizeTo( + original.width + delta.width, + original.height + delta.height + ); + }); + }); }, testNodeConstants: function() { @@ -1600,10 +1628,14 @@ new Test.Unit.Runner({ }, testElementPurge: function() { + function uidForElement(elem) { + return elem.uniqueID ? elem.uniqueID : elem._prototypeUID; + } + var element = new Element('div'); element.store('foo', 'bar'); - var uid = element._prototypeUID; + var uid = uidForElement(element); this.assert(uid in Element.Storage, "newly-created element's uid should exist in `Element.Storage`"); var storageKeysBefore = Object.keys(Element.Storage).length; @@ -1647,14 +1679,20 @@ new Test.Unit.Runner({ function preservingBrowserDimensions(callback) { var original = document.viewport.getDimensions(); + window.resizeTo(640, 480); + var resized = document.viewport.getDimensions(); original.width += 640 - resized.width, original.height += 480 - resized.height; try { window.resizeTo(original.width, original.height); callback(); - } finally { + } catch(e) { + throw e; + }finally { window.resizeTo(original.width, original.height); } } + + From 61ff60641fe0e9882ad9048f13b2007eaa7a472f Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:42:11 -0600 Subject: [PATCH 313/502] Add note explaining IE6's test failure for `position: fixed` in layout.js. --- test/unit/layout_test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit/layout_test.js b/test/unit/layout_test.js index 7315dce6c..03d61d165 100644 --- a/test/unit/layout_test.js +++ b/test/unit/layout_test.js @@ -121,6 +121,8 @@ new Test.Unit.Runner({ var viewportWidth = document.viewport.getWidth(); var layout = $('box9').getLayout(); + this.info("NOTE: IE6 WILL fail these tests because it doesn't support position: fixed. This is expected."); + function assertNear(v1, v2, message) { var abs = Math.abs(v1 - v2); this.assert(abs <= 1, message + ' (actual: ' + v1 + ', ' + v2 + ')'); @@ -132,7 +134,8 @@ new Test.Unit.Runner({ assertNear.call(this, vWidth, eWidth, 'width (visible)'); $('box9').hide(); - assertNear.call(this, vWidth, $('box9').measure('width'), 'width (hidden)'); + assertNear.call(this, vWidth, $('box9').measure('width'), 'width (hidden)'); + $('box9').show(); }, 'test #toCSS, #toObject, #toHash': function() { From cc5752eaa8fe795752a1ca8e8abc8666af31fcf3 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 1 Dec 2011 17:43:03 -0600 Subject: [PATCH 314/502] Update event.js tests to use the new event registry conventions. Fixes failing tests. --- test/unit/event_test.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/unit/event_test.js b/test/unit/event_test.js index ba90b9f69..de274cb3b 100644 --- a/test/unit/event_test.js +++ b/test/unit/event_test.js @@ -160,19 +160,23 @@ new Test.Unit.Runner({ span.observe("test:somethingHappened", observer); - var registry = span.getStorage().get('prototype_event_registry'); + function uidForElement(elem) { + return elem.uniqueID ? elem.uniqueID : elem._prototypeUID; + } - this.assert(registry); - this.assert(Object.isArray(registry.get('test:somethingHappened'))); - this.assertEqual(1, registry.get('test:somethingHappened').length); + var registry = Event.cache[uidForElement(span)]; + + this.assert(registry, 'registry should exist'); + this.assert(Object.isArray(registry['test:somethingHappened'])); + this.assertEqual(1, registry['test:somethingHappened'].length); span.stopObserving("test:somethingHappened", observer); - registry = span.getStorage().get('prototype_event_registry'); + registry = Event.cache[uidForElement(span)]; this.assert(registry); - this.assert(Object.isArray(registry.get('test:somethingHappened'))); - this.assertEqual(0, registry.get('test:somethingHappened').length); + this.assert(Object.isArray(registry['test:somethingHappened'])); + this.assertEqual(0, registry['test:somethingHappened'].length); }, testObserveAndStopObservingAreChainable: function() { From d9aeeed07de24175f918f12ade3679c6958cd287 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 2 Dec 2011 18:34:37 -0600 Subject: [PATCH 315/502] Use `Object.prototype.hasOwnProperty` within `Object.keys`; also manually check for properties like `toString` to work around IE's DontEnum bug. (Andy Earnshaw, kangax, Andrew Dupont) --- src/prototype/lang/object.js | 28 ++++++++++++++++++++++++++-- test/unit/object_test.js | 9 +++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/prototype/lang/object.js b/src/prototype/lang/object.js index 32ab541bf..0c5a4f2a5 100644 --- a/src/prototype/lang/object.js +++ b/src/prototype/lang/object.js @@ -22,6 +22,7 @@ (function() { var _toString = Object.prototype.toString, + _hasOwnProperty = Object.prototype.hasOwnProperty, NULL_TYPE = 'Null', UNDEFINED_TYPE = 'Undefined', BOOLEAN_TYPE = 'Boolean', @@ -39,6 +40,21 @@ JSON.stringify(0) === '0' && typeof JSON.stringify(Prototype.K) === 'undefined'; + + + var DONT_ENUMS = ['toString', 'toLocaleString', 'valueOf', + 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor']; + + // Some versions of JScript fail to enumerate over properties, names of which + // correspond to non-enumerable properties in the prototype chain + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + // check actual property name, so that it works with augmented Object.prototype + if (p === 'toString') return false; + } + return true; + })(); + function Type(o) { switch(o) { case null: return NULL_TYPE; @@ -52,7 +68,7 @@ } return OBJECT_TYPE; } - + /** * Object.extend(destination, source) -> Object * - destination (Object): The object to receive the new properties. @@ -309,10 +325,18 @@ if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; for (var property in object) { - if (object.hasOwnProperty(property)) { + if (_hasOwnProperty.call(object, property)) results.push(property); + } + + // Account for the DontEnum properties in affected browsers. + if (IS_DONTENUM_BUGGY) { + for (var i = 0; property = DONT_ENUMS[i]; i++) { + if (_hasOwnProperty.call(object, property)) + results.push(property); } } + return results; } diff --git a/test/unit/object_test.js b/test/unit/object_test.js index 1d213fe04..782992e79 100644 --- a/test/unit/object_test.js +++ b/test/unit/object_test.js @@ -31,6 +31,15 @@ new Test.Unit.Runner({ Foo.prototype.foo = 'foo'; this.assertEnumEqual(['bar'], Object.keys(new Foo())); this.assertRaise('TypeError', function(){ Object.keys() }); + + var obj = { + foo: 'bar', + baz: 'thud', + toString: function() { return '1'; }, + valueOf: function() { return 1; } + }; + + this.assertEqual(4, Object.keys(obj).length, 'DontEnum properties should be included in Object.keys'); }, testObjectInspect: function() { From 5968d828337bc235f7e958e747ba70c94bb9b37e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 5 Dec 2011 16:28:25 -0600 Subject: [PATCH 316/502] Set initial value of Element.UID to 1 so it doesn't collide with the UID for `window`. (Victor) [#1292 state:resolved] --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index d857a750c..9dc93e04a 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3023,7 +3023,7 @@ // STORAGE var UID = 0; - GLOBAL.Element.Storage = { UID: 0 }; + GLOBAL.Element.Storage = { UID: 1 }; function getUniqueElementID(element) { if (element === window) return 0; From f01a7f19b2068a439079d4c0a61ddf2ae78f1dd6 Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 6 Dec 2011 08:43:35 +0000 Subject: [PATCH 317/502] documentation typo fixed --- src/prototype/dom/selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/selector.js b/src/prototype/dom/selector.js index 61e21afda..37fdb7c8f 100644 --- a/src/prototype/dom/selector.js +++ b/src/prototype/dom/selector.js @@ -125,7 +125,7 @@ Prototype.Selector = (function() { * Prototype.Selector.find(elements, expression[, index = 0]) -> Element * - elements (Enumerable): a collection of DOM elements. * - expression (String): A CSS selector. - * - index: Numeric index of the match to return, defaults to 0. + * - index (Number): Numeric index of the match to return, defaults to 0. * * Filters the given collection of elements with `expression` and returns the * first matching element (or the `index`th matching element if `index` is From 9d920de61446a451450a07fa8513be22a3f66003 Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Tue, 6 Dec 2011 12:09:28 +0000 Subject: [PATCH 318/502] documentation typo fixed. hopefully the last --- src/prototype/dom/dom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 9dc93e04a..7eb055adf 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1468,6 +1468,7 @@ /** * Element.siblings(@element) -> [Element...] + * * Collects all of element's siblings and returns them as an [[Array]] of * elements. * From 20e0bd52b41bcc4e65e7fb6d62f74116530838a4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 23 Dec 2011 18:57:00 -0600 Subject: [PATCH 319/502] Fix the encoding of form values that contain spaces and newlines (in accordance with the HTML spec and browser behavior). [#1134 state:resolved] --- src/prototype/lang/hash.js | 12 +++++++++++- test/unit/hash_test.js | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/prototype/lang/hash.js b/src/prototype/lang/hash.js index 57d43f996..a04aeb1a7 100644 --- a/src/prototype/lang/hash.js +++ b/src/prototype/lang/hash.js @@ -288,7 +288,17 @@ var Hash = Class.create(Enumerable, (function() { // Private. No PDoc necessary. function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); + + var value = String.interpret(value); + + // Normalize newlines as \r\n because the HTML spec says newlines should + // be encoded as CRLFs. + value = value.gsub(/(\r)?\n/, '\r\n'); + value = encodeURIComponent(value); + // Likewise, according to the spec, spaces should be '+' rather than + // '%20'. + value = value.gsub(/%20/, '+'); + return key + '=' + value; } /** related to: String#toQueryParams diff --git a/test/unit/hash_test.js b/test/unit/hash_test.js index cf3bcbde1..27df4e80a 100644 --- a/test/unit/hash_test.js +++ b/test/unit/hash_test.js @@ -130,6 +130,15 @@ new Test.Unit.Runner({ this.assertEqual("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.multiple_special).toQueryString()); this.assertHashEqual(Fixtures.multiple_special, $H(Fixtures.multiple_special).toQueryString().toQueryParams()); this.assertIdentical(Object.toQueryString, Hash.toQueryString); + + // Serializing newlines and spaces is weird. See: + // http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13.4.1 + var complex = "an arbitrary line\n\'something in single quotes followed by a newline\'\r\n" + + "and more text eventually"; + var queryString = $H({ val: complex }).toQueryString(); + var expected = "val=an+arbitrary+line%0D%0A'something+in+single+quotes+followed+by+a+" + + "newline'%0D%0Aand+more+text+eventually"; + this.assertEqual(expected, queryString, "newlines and spaces should be properly encoded"); }, testInspect: function() { From 2f5b6338ab5bcdb393a4074c36af60ba4dc69af4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 17:31:00 -0600 Subject: [PATCH 320/502] Add comments explaining some of the changes to array.js. --- src/prototype/lang/array.js | 38 ++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 6ecb1e9af..124c22301 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -447,6 +447,9 @@ Array.from = $A; } // Replaces a built-in function. No PDoc needed. + // + // Used instead of the broken version of Array#concat in some versions of + // Opera. Made to be ES5-compliant. function concat(_) { var array = [], items = slice.call(arguments, 0), item, n = 0; items.unshift(this); @@ -465,15 +468,33 @@ Array.from = $A; return array; } + // Certain ES5 array methods have the same names as Prototype array methods + // and perform the same functions. + // + // Prototype's implementations of these methods differ from the ES5 spec in + // the way a missing iterator function is handled. Prototype uses + // `Prototype.K` as a default iterator, while ES5 specifies that a + // `TypeError` must be thrown. Implementing the ES5 spec completely would + // break backward compatibility and would force users to pass `Prototype.K` + // manually. + // + // Instead, if native versions of these methods exist, we wrap the existing + // methods with our own behavior. This has very little performance impact. + // It violates the spec by suppressing `TypeError`s for certain methods, + // but that's an acceptable trade-off. function wrapNative(method) { return function() { if (arguments.length === 0) { + // No iterator was given. Instead of throwing a `TypeError`, use + // `Prototype.K` as the default iterator. return method.call(this, Prototype.K); } else if (arguments[0] === undefined) { + // Same as above. var args = slice.call(arguments, 1); - args.unshift(Prototype.K) + args.unshift(Prototype.K); return method.apply(this, args); } else { + // Pass straight through to the native method. return method.apply(this, arguments); } }; @@ -487,7 +508,7 @@ Array.from = $A; var results = [], context = arguments[1], n = 0; for (var i = 0, length = this.length; i < length; i++) { - if (i in this) { + if (i in this) results[n] = iterator.call(context, this[i], i, this); } n++; @@ -498,10 +519,13 @@ Array.from = $A; } if (arrayProto.filter) { + // `Array#filter` requires an iterator by nature, so we don't need to + // wrap it. var filter = Array.prototype.filter; } else { function filter(iterator) { - if (!Object.isFunction(iterator)) { throw new TypeError(); } + if (!Object.isFunction(iterator)) + throw new TypeError(); var results = [], context = arguments[1], value; for (var i = 0, length = this.length; i < length; i++) { @@ -550,14 +574,14 @@ Array.from = $A; } } + // Prototype's `Array#inject` behaves identically to ES5's `Array#reduce`. if (arrayProto.reduce) { - // Keep a copy of the native reduce var _reduce = arrayProto.reduce; function inject(memo, iterator) { iterator = iterator || Prototype.K; var context = arguments[2]; - // The iterator has to be bound, as Array.prototype.reduce - // always executes the iterator in the global context. + // The iterator must be bound, as `Array#reduce` always executes the + // iterator in the global context. return _reduce.call(this, iterator.bind(context), memo, context); } } else { @@ -605,7 +629,7 @@ Array.from = $A; if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; - // use native browser JS 1.6 implementation if available + // Use native browser JS 1.6 implementations if available. if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; })(); From b953bf211ce0ad423757c42efba10814a734b799 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 17:46:23 -0600 Subject: [PATCH 321/502] Add missing brace. --- src/prototype/lang/array.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 124c22301..06d017217 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -508,7 +508,7 @@ Array.from = $A; var results = [], context = arguments[1], n = 0; for (var i = 0, length = this.length; i < length; i++) { - if (i in this) + if (i in this) { results[n] = iterator.call(context, this[i], i, this); } n++; From 0bf9cd1674da5dd39f9980f0e9b78601b78e6ac5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 18:02:28 -0600 Subject: [PATCH 322/502] Fix the way we handle positioning in `Element#clonePosition` when the source element is absolutely-positioned and has the BODY as its offset parent. [#1286 state:resolved] (Luis Fernando Planella Gonzalez, Victor, Dan Popescu) --- src/prototype/dom/layout.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index 90dd70a9c..9b7b78f31 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1508,22 +1508,15 @@ // Find page position of source. source = $(source); element = $(element); - var p = Element.viewportOffset(source), delta = [0, 0], parent = null; + var p = Element.viewportOffset(source), delta = [0, 0]; // A delta of 0/0 will work for `positioned: fixed` elements, but // for `position: absolute` we need to get the parent's offset. if (Element.getStyle(element, 'position') === 'absolute') { - parent = Element.getOffsetParent(element); - delta = Element.viewportOffset(parent); + var parent = Element.getOffsetParent(element); + if (parent !== document.body) delta = Element.viewportOffset(parent); } - // Adjust by BODY offsets. Fixes some versions of safari. - if (parent === document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - var layout = Element.getLayout(source); // Set position. @@ -1532,10 +1525,10 @@ if (options.setLeft) styles.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) - styles.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + styles.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) - styles.width = layout.get('border-box-width') + 'px'; + styles.width = layout.get('border-box-width') + 'px'; if (options.setHeight) styles.height = layout.get('border-box-height') + 'px'; From fe31e6bc2126101fd907d8c36bb52fb0a681c35b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 18:16:53 -0600 Subject: [PATCH 323/502] Be precise with my language. --- src/prototype/lang/array.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 06d017217..9da031e61 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -574,7 +574,7 @@ Array.from = $A; } } - // Prototype's `Array#inject` behaves identically to ES5's `Array#reduce`. + // Prototype's `Array#inject` behaves similarly to ES5's `Array#reduce`. if (arrayProto.reduce) { var _reduce = arrayProto.reduce; function inject(memo, iterator) { From c312cbc1d33a08b41fa5710c2e8aa20e8963e58e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 18:36:07 -0600 Subject: [PATCH 324/502] Build with the submodule version Sizzle if it exists; otherwise build from the default Sizzle in `src/sizzle.js`. (We still won't initiate a fetch of the Sizzle submodule, so if the user wants this behavior he/she should grab the submodule manually.) [#1249 state:resolved] --- Rakefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index c2b744ce4..aeab1959b 100755 --- a/Rakefile +++ b/Rakefile @@ -157,10 +157,19 @@ EOF end def self.get_selector_engine(name) - return if name == DEFAULT_SELECTOR_ENGINE || !name + return if !name + # If the submodule exists, we should use it, even if we're using the + # default engine; the user might have fetched it manually, and thus would + # want to build a distributable with the most recent version of that + # engine. submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) return submodule_path if name === "legacy_selector" + + # If it doesn't exist, we should fetch it, _unless_ it's the default + # engine. We've already got a known version of the default engine in our + # load path. + return if name == DEFAULT_SELECTOR_ENGINE get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" From bd3965d82d8736132c76b17252232f54980b0772 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 18:41:01 -0600 Subject: [PATCH 325/502] Move function declarations outside of conditionals in the name of spec compliance. --- src/prototype/lang/array.js | 126 ++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 62 deletions(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 9da031e61..68515b67c 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -500,91 +500,93 @@ Array.from = $A; }; } + function map(iterator) { + iterator = iterator || Prototype.K; + var results = [], context = arguments[1], n = 0; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this) { + results[n] = iterator.call(context, this[i], i, this); + } + n++; + } + results.length = n; + return results; + } + if (arrayProto.map) { - var map = wrapNative(Array.prototype.map); - } else { - function map(iterator) { - iterator = iterator || Prototype.K; - var results = [], context = arguments[1], n = 0; - - for (var i = 0, length = this.length; i < length; i++) { - if (i in this) { - results[n] = iterator.call(context, this[i], i, this); + map = wrapNative(Array.prototype.map); + } + + function filter(iterator) { + if (!Object.isFunction(iterator)) + throw new TypeError(); + var results = [], context = arguments[1], value; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this) { + value = this[i]; + if (iterator.call(context, value, i, this)) { + results.push(value); } - n++; } - results.length = n; - return results; } + return results; } - + if (arrayProto.filter) { // `Array#filter` requires an iterator by nature, so we don't need to // wrap it. - var filter = Array.prototype.filter; - } else { - function filter(iterator) { - if (!Object.isFunction(iterator)) - throw new TypeError(); - var results = [], context = arguments[1], value; - - for (var i = 0, length = this.length; i < length; i++) { - if (i in this) { - value = this[i]; - if (iterator.call(context, value, i, this)) { - results.push(value); - } - } + filter = Array.prototype.filter; + } + + function some(iterator) { + iterator = iterator || Prototype.K; + var context = arguments[1]; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this && iterator.call(context, this[i], i, this)) { + return true; } - return results; } + + return false; } if (arrayProto.some) { var some = wrapNative(Array.prototype.some); - } else { - function some(iterator) { - iterator = iterator || Prototype.K; - var context = arguments[1]; - - for (var i = 0, length = this.length; i < length; i++) { - if (i in this && iterator.call(context, this[i], i, this)) { - return true; - } + } + + function every(iterator) { + iterator = iterator || Prototype.K; + var context = arguments[1]; + + for (var i = 0, length = this.length; i < length; i++) { + if (i in this && !iterator.call(context, this[i], i, this)) { + return false; } - - return false; } + + return true; } if (arrayProto.every) { var every = wrapNative(Array.prototype.every); - } else { - function every(iterator) { - iterator = iterator || Prototype.K; - var context = arguments[1]; - - for (var i = 0, length = this.length; i < length; i++) { - if (i in this && !iterator.call(context, this[i], i, this)) { - return false; - } - } - - return true; - } } // Prototype's `Array#inject` behaves similarly to ES5's `Array#reduce`. - if (arrayProto.reduce) { - var _reduce = arrayProto.reduce; - function inject(memo, iterator) { - iterator = iterator || Prototype.K; - var context = arguments[2]; - // The iterator must be bound, as `Array#reduce` always executes the - // iterator in the global context. - return _reduce.call(this, iterator.bind(context), memo, context); - } - } else { + var _reduce = arrayProto.reduce; + function inject(memo, iterator) { + iterator = iterator || Prototype.K; + var context = arguments[2]; + // The iterator must be bound, as `Array#reduce` always executes the + // iterator in the global context. + return _reduce.call(this, iterator.bind(context), memo, context); + } + + // Piggyback on `Array#reduce` if it exists; otherwise fall back to the + // standard `Enumerable.inject`. + if (!arrayProto.reduce) { var inject = Enumerable.inject; } From 460be4691af9e4e92908c9ac707597bad69be57c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 17 Feb 2012 19:03:31 -0600 Subject: [PATCH 326/502] Further tweaks to array methods for ES5 compatibility. --- src/prototype/lang/array.js | 46 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 68515b67c..d45bac409 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -500,13 +500,23 @@ Array.from = $A; }; } + // Note that #map, #filter, #some, and #every take some extra steps for + // ES5 compliance: the context in which they're called is coerced to an + // object, and that object's `length` property is coerced to a finite + // integer. This makes it easier to use the methods as generics. + // + // This means that they behave a little differently from other methods in + // `Enumerable`/`Array` that don't collide with ES5, but that's OK. function map(iterator) { + if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; + + var object = Object(this); var results = [], context = arguments[1], n = 0; - for (var i = 0, length = this.length; i < length; i++) { - if (i in this) { - results[n] = iterator.call(context, this[i], i, this); + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object) { + results[n] = iterator.call(context, object[i], i, object); } n++; } @@ -519,14 +529,16 @@ Array.from = $A; } function filter(iterator) { - if (!Object.isFunction(iterator)) + if (this == null || !Object.isFunction(iterator)) throw new TypeError(); + + var object = Object(this); var results = [], context = arguments[1], value; - for (var i = 0, length = this.length; i < length; i++) { - if (i in this) { - value = this[i]; - if (iterator.call(context, value, i, this)) { + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object) { + value = object[i]; + if (iterator.call(context, value, i, object)) { results.push(value); } } @@ -541,11 +553,13 @@ Array.from = $A; } function some(iterator) { + if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; var context = arguments[1]; - for (var i = 0, length = this.length; i < length; i++) { - if (i in this && iterator.call(context, this[i], i, this)) { + var object = Object(this); + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object && iterator.call(context, object[i], i, object)) { return true; } } @@ -558,11 +572,13 @@ Array.from = $A; } function every(iterator) { + if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; var context = arguments[1]; - for (var i = 0, length = this.length; i < length; i++) { - if (i in this && !iterator.call(context, this[i], i, this)) { + var object = Object(this); + for (var i = 0, length = object.length >>> 0; i < length; i++) { + if (i in object && !iterator.call(context, object[i], i, object)) { return false; } } @@ -579,9 +595,9 @@ Array.from = $A; function inject(memo, iterator) { iterator = iterator || Prototype.K; var context = arguments[2]; - // The iterator must be bound, as `Array#reduce` always executes the - // iterator in the global context. - return _reduce.call(this, iterator.bind(context), memo, context); + // The iterator must be bound, as `Array#reduce` always binds to + // `undefined`. + return _reduce.call(this, iterator.bind(context), memo); } // Piggyback on `Array#reduce` if it exists; otherwise fall back to the From c9ee1c4ea1a11aeb88578f37c5e6dfee6cf9901a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 18 Feb 2012 01:57:52 -0600 Subject: [PATCH 327/502] Make `Function#bind` as ES5-compliant as possible. --- src/prototype/lang/function.js | 20 +++++++++++++++++--- test/unit/function_test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/prototype/lang/function.js b/src/prototype/lang/function.js index 9850d3bf5..3a2f8794c 100644 --- a/src/prototype/lang/function.js +++ b/src/prototype/lang/function.js @@ -105,13 +105,27 @@ Object.extend(Function.prototype, (function() { * * (To curry without binding, see [[Function#curry]].) **/ + function bind(context) { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + if (arguments.length < 2 && Object.isUndefined(arguments[0])) + return this; + + if (!Object.isFunction(this)) + throw new TypeError("The object is not callable."); + + var nop = function() {}; var __method = this, args = slice.call(arguments, 1); - return function() { + + var bound = function() { var a = merge(args, arguments); - return __method.apply(context, a); + var c = this instanceof nop ? this : context || window; + return __method.apply(c, a); } + + nop.prototype = this.prototype; + bound.prototype = new nop(); + + return bound; } /** related to: Function#bind diff --git a/test/unit/function_test.js b/test/unit/function_test.js index 2a9e8b257..72bdcfde6 100644 --- a/test/unit/function_test.js +++ b/test/unit/function_test.js @@ -44,6 +44,36 @@ new Test.Unit.Runner({ methodWithArguments.bind({ hi: 'withBindArgs' }, 'arg1', 'arg2')()); this.assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4', methodWithArguments.bind({ hi: 'withBindArgsAndArgs' }, 'arg1', 'arg2')('arg3', 'arg4')); + + + // Ensure that bound functions ignore their `context` when used as + // constructors. Taken from example at: + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype.toString = function() { + return this.x + "," + this.y; + }; + + var p = new Point(1, 2); + p.toString(); // "1,2" + + var emptyObj = {}; + var YAxisPoint = Point.bind(emptyObj, 0 /* x */); + + var axisPoint = new YAxisPoint(5); + axisPoint.toString(); // "0,5" + + this.assertEqual("0,5", axisPoint.toString(), + "bound constructor should ignore context and curry properly"); + + this.assert(axisPoint instanceof Point, + "should be an instance of Point"); + this.assert(axisPoint instanceof YAxisPoint, + "should be an instance of YAxisPoint"); }, testFunctionCurry: function() { From 43adb8b4ecabc6855324e07111c3fad6627861ea Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 18 Feb 2012 02:50:11 -0600 Subject: [PATCH 328/502] Fix incorrect testing of `Array#each` on sparse arrays. --- test/unit/array_test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/array_test.js b/test/unit/array_test.js index 2c506786a..d1f11c3f6 100644 --- a/test/unit/array_test.js +++ b/test/unit/array_test.js @@ -216,9 +216,13 @@ new Test.Unit.Runner({ }, testEachOnSparseArrays: function() { + var counter = 0; + var sparseArray = [0, 1]; sparseArray[5] = 5; - this.assertEqual('[0, 1, 5]', sparseArray.inspect(), "Array#each should skip nonexistent keys in an array"); + sparseArray.each( function(item) { counter++; }); + + this.assertEqual(3, counter, "Array#each should skip nonexistent keys in an array"); }, testMapGeneric: function() { From 308917bac21e310954f620f46562f321064fd36a Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Mon, 20 Feb 2012 13:26:41 +0300 Subject: [PATCH 329/502] PDoc fixed --- src/prototype/lang/string.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 7b58b5c3e..a8807d3dc 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -177,7 +177,7 @@ Object.extend(String.prototype, (function() { * ##### Examples * * 'apple, pear & orange'.scan(/\w+/, alert); - * // -> 'apple pear orange' (and displays 'apple', 'pear' and 'orange' in three successive alert dialogs) + * // -> 'apple pear & orange' (and displays 'apple', 'pear' and 'orange' in three successive alert dialogs) * * Can be used to populate an array: * @@ -382,11 +382,9 @@ Object.extend(String.prototype, (function() { * } * * (You can leave off the `window.` part of that, but it's bad form.) - * Evaluates the content of any `script` block present in the string. Returns - * an array containing the value returned by each script. **/ function evalScripts() { - return this.extractScripts().map(function(script) { return eval(script) }); + return this.extractScripts().map(function(script) { return eval(script); }); } /** related to: String#unescapeHTML @@ -451,7 +449,7 @@ Object.extend(String.prototype, (function() { * 'section=blog&id=45'.toQueryParams(); * // -> {section: 'blog', id: '45'} * - * 'section=blog;id=45'.toQueryParams(); + * 'section=blog;id=45'.toQueryParams(';'); * // -> {section: 'blog', id: '45'} * * 'http://www.example.com?section=blog&id=45#comments'.toQueryParams(); From a8613ef8c213b86e1722278d364b7633e80fa4ec Mon Sep 17 00:00:00 2001 From: leaf Date: Thu, 23 Feb 2012 14:40:31 -0800 Subject: [PATCH 330/502] Fixed incorrect documentation. --- src/prototype/dom/event.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 415193898..6808bfe3d 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -253,8 +253,7 @@ * its ancestor chain. If `expression` is not given, the element which fired * the event is returned. * - * *If no matching element is found, the document itself (`HTMLDocument` node) - * is returned.* + * *If no matching element is found, `undefined` is returned.* * * ##### Example * From 6c7ffe29cd3604be4081a9510cb2015fcc377ae4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 4 Mar 2012 01:00:31 -0600 Subject: [PATCH 331/502] Make `Prototype.ScriptFragment` properly handle whitespace in a closing SCRIPT tag. [#1297 state:resolved] (piopier, Victor) --- src/prototype/prototype.js | 2 +- test/unit/string_test.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/prototype/prototype.js b/src/prototype/prototype.js index 7ce3270c4..ef5b04604 100644 --- a/src/prototype/prototype.js +++ b/src/prototype/prototype.js @@ -139,7 +139,7 @@ var Prototype = { })() }, - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + ScriptFragment: ']*>([\\S\\s]*?)<\/script\\s*>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, /** diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 2851950f3..d729f2b12 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -215,6 +215,9 @@ new Test.Unit.Runner({ this.assertEqual('foo bar', 'foo bar'.stripScripts()); this.assertEqual('foo bar', ('foo " + + str.evalScripts.bind(str).defer(); + + this.wait(50, function() { + this.assert(window.deferBoundProperlyOnString); + }); + + }); + }); }); + + }, testFunctionMethodize: function() { From 4cace0b8a86ad32949992b658477d47710c86fa2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 4 Jun 2012 23:55:11 -0500 Subject: [PATCH 358/502] Be more precise with some tests Chrome dislikes. --- test/unit/dom_test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index c81adb5e7..c00fcff67 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -70,7 +70,7 @@ new Test.Unit.Runner({ 'getElementById and $ should return the same element'); this.assertEnumEqual([ $('testdiv'), $('container') ], $('testdiv', 'container')); - this.assertEnumEqual([ $('testdiv'), undefined, $('container') ], + this.assertEnumEqual([ $('testdiv'), null, $('container') ], $('testdiv', 'noWayThisIDExists', 'container')); var elt = $('testdiv'); this.assertIdentical(elt, $(elt)); @@ -1103,10 +1103,10 @@ new Test.Unit.Runner({ testElementWriteAttributeWithIssues: function() { var input = $('write_attribute_input').writeAttribute({maxlength: 90, tabindex: 10}), td = $('write_attribute_td').writeAttribute({valign: 'bottom', colspan: 2, rowspan: 2}); - this.assertEqual(90, input.readAttribute('maxlength')); - this.assertEqual(10, input.readAttribute('tabindex')); - this.assertEqual(2, td.readAttribute('colspan')); - this.assertEqual(2, td.readAttribute('rowspan')); + this.assertEqual("90", input.readAttribute('maxlength')); + this.assertEqual("10", input.readAttribute('tabindex')); + this.assertEqual("2", td.readAttribute('colspan')); + this.assertEqual("2", td.readAttribute('rowspan')); this.assertEqual('bottom', td.readAttribute('valign')); var p = $('write_attribute_para'), label = $('write_attribute_label'); From f64393e13b33ef61c435f9e03ad610e75e59df0d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 01:21:35 -0500 Subject: [PATCH 359/502] Fix a URIError encountered in Chrome when interpreting an `X-JSON` header with extended characters. --- src/prototype/ajax/response.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/prototype/ajax/response.js b/src/prototype/ajax/response.js index 5054909b1..7b7009270 100644 --- a/src/prototype/ajax/response.js +++ b/src/prototype/ajax/response.js @@ -151,7 +151,23 @@ Ajax.Response = Class.create({ _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; - json = decodeURIComponent(escape(json)); + + try { + // Browsers expect HTTP headers to be ASCII and nothing else. Running + // them through `decodeURIComponent` processes them with the page's + // specified encoding. + json = decodeURIComponent(escape(json)); + } catch(e) { + // Except Chrome doesn't seem to need this, and calling + // `decodeURIComponent` on text that's already in the proper encoding + // will throw a `URIError`. The ugly solution is to assume that a + // `URIError` raised here signifies that the text is, in fact, already + // in the correct encoding, and treat the failure as a good sign. + // + // This is ugly, but so too is sending extended characters in an HTTP + // header with no spec to back you up. + } + try { return json.evalJSON(this.request.options.sanitizeJSON || !this.request.isSameOrigin()); From 2d41b5181f3aad1e82511dd46d21ea308e0d81a2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 03:25:15 -0500 Subject: [PATCH 360/502] Fix comment. --- src/prototype/lang/class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/class.js b/src/prototype/lang/class.js index 0e5726478..5e6696549 100644 --- a/src/prototype/lang/class.js +++ b/src/prototype/lang/class.js @@ -174,7 +174,7 @@ var Class = (function() { // on the nuanced behavior of whatever `bind` implementation is on // the page. // - // MDC's polyfill, for instance, doesn't like binding methods that + // MDC's polyfill, for instance, doesn't like binding functions that // haven't got a `prototype` property defined. value.valueOf = (function(method) { return function() { return method.valueOf.call(method); }; From 5d404d48848b61d2df9a86a4c53e5bfe2c4f88f8 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 03:25:55 -0500 Subject: [PATCH 361/502] Skip window-resizing tests if programmatic window resizing isn't enabled by the browser. --- test/unit/dom_test.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index c00fcff67..425261c42 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -7,6 +7,8 @@ var createParagraph = function(text) { return p; } +var RESIZE_DISABLED = false; + function simulateClick(node) { var oEvent; if (document.createEvent) { @@ -1473,9 +1475,19 @@ new Test.Unit.Runner({ window.resizeBy(50, 50); this.wait(1000, function() { var after = document.viewport.getDimensions(); + + // Assume that JavaScript window resizing is disabled if before width + // and after width are the same. + if (before.width === after.width) { + RESIZE_DISABLED = true; + this.info("SKIPPING REMAINING TESTS (JavaScript window resizing disabled)"); + return; + } - this.assertEqual(before.width + 50, after.width, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); - this.assertEqual(before.height + 50, after.height, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); + this.assertEqual(before.width + 50, after.width, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); + this.assertEqual(before.height + 50, after.height, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS"); this.wait(1000, function() { // Restore original dimensions. @@ -1512,6 +1524,11 @@ new Test.Unit.Runner({ window.scrollTo(0, 35); this.assertEqual(35, document.viewport.getScrollOffsets().top); + if (RESIZE_DISABLED) { + this.info("SKIPPING REMAINING TESTS (JavaScript window resizing disabled)"); + return; + } + window.resizeTo(200, 650); this.wait(1000, function() { @@ -1519,7 +1536,8 @@ new Test.Unit.Runner({ var delta = { width: 200 - before.width, height: 650 - before.height }; window.scrollTo(25, 35); - this.assertEqual(25, document.viewport.getScrollOffsets().left, "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THESE TESTS TO PASS"); + this.assertEqual(25, document.viewport.getScrollOffsets().left, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THESE TESTS TO PASS"); this.wait(1000, function() { // Restore original dimensions. From 4f00c9e76d95f20dbe7af5da3084740a8e9cec2a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 04:13:18 -0500 Subject: [PATCH 362/502] Add notes in the documentation about the methods that act as ES5 polyfills. --- src/prototype/lang/array.js | 84 ++++++++++++++++++++++++++++++++++ src/prototype/lang/function.js | 6 +++ 2 files changed, 90 insertions(+) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 71b7e3de9..464656860 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -411,6 +411,12 @@ Array.from = $A; * or `-1` if `item` doesn't exist in the array. `Array#indexOf` compares * items using *strict equality* (`===`). * + * `Array#indexOf` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.14) for more + * information. + * * ##### Examples * * [3, 5, 6, 1, 20].indexOf(1) @@ -460,6 +466,12 @@ Array.from = $A; * * Returns the position of the last occurrence of `item` within the * array — or `-1` if `item` doesn't exist in the array. + * + * `Array#lastIndexOf` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.15) for more + * information. **/ function lastIndexOf(item, i) { if (this == null) throw new TypeError(); @@ -529,6 +541,7 @@ Array.from = $A; // methods with our own behavior. This has very little performance impact. // It violates the spec by suppressing `TypeError`s for certain methods, // but that's an acceptable trade-off. + function wrapNative(method) { return function() { if (arguments.length === 0) { @@ -554,6 +567,24 @@ Array.from = $A; // // This means that they behave a little differently from other methods in // `Enumerable`/`Array` that don't collide with ES5, but that's OK. + + /** + * Array#map([iterator = Prototype.K[, context]]) -> Array + * - iterator (Function): The iterator function to apply to each element + * in the enumeration. + * - context (Object): An optional object to use as `this` within + * calls to the iterator. + * + * Returns the result of applying `iterator` to each item in the array. If + * no iterator is provided, the elements are simply copied to the returned + * array. + * + * `Array#map` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.19) for more + * information. + **/ function map(iterator) { if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; @@ -575,6 +606,22 @@ Array.from = $A; map = wrapNative(Array.prototype.map); } + /** + * Array#filter(iterator[, context]) -> Array + * - iterator (Function): An iterator function to use to test the + * elements. + * - context (Object): An optional object to use as `this` within + * calls to the iterator. + * + * Returns a new array containing all the items in this array for which + * `iterator` returned a truthy value. + * + * `Array#filter` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.20) for more + * information. + **/ function filter(iterator) { if (this == null || !Object.isFunction(iterator)) throw new TypeError(); @@ -599,6 +646,24 @@ Array.from = $A; filter = Array.prototype.filter; } + /** + * Array#some([iterator = Prototype.K[, context]]) -> Array + * - iterator (Function): An optional function to use to evaluate each + * element in the enumeration; the function should return the value to + * test. If this is not provided, the element itself is tested. + * - context (Object): An optional object to use as `this` within + * calls to the iterator. + * + * Returns the result of applying `iterator` to each item in the array. If + * no iterator is provided, the elements are simply copied to the returned + * array. + * + * `Array#some` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.17) for more + * information. + **/ function some(iterator) { if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; @@ -618,6 +683,25 @@ Array.from = $A; var some = wrapNative(Array.prototype.some); } + + /** + * Array#every([iterator = Prototype.K[, context]]) -> Boolean + * - iterator (Function): An optional function to use to evaluate each + * element in the enumeration; the function should return the value to + * test. If this is not provided, the element itself is tested. + * - context (Object): An optional object to use as `this` within + * calls to the iterator. + * + * Determines whether at least one element is truthy (boolean-equivalent to + * `true`), either directly or through computation by the provided iterator. + * + * `Array#every` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.4.4.16) for more + * information. + * + **/ function every(iterator) { if (this == null) throw new TypeError(); iterator = iterator || Prototype.K; diff --git a/src/prototype/lang/function.js b/src/prototype/lang/function.js index a182c7dc7..2d9d71b1f 100644 --- a/src/prototype/lang/function.js +++ b/src/prototype/lang/function.js @@ -52,6 +52,12 @@ Object.extend(Function.prototype, (function() { * function is called, it will call the original ensuring that `this` is set * to `context`. Also optionally curries arguments for the function. * + * `Function#bind` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.3.4.5) for more + * information. + * * ##### Examples * * A typical use of [[Function#bind]] is to ensure that a callback (event From 0d62e34e52c6f91e3c4f557b8df01e7b0484f98e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 05:00:30 -0500 Subject: [PATCH 363/502] Document `Object.keys` as a polyfill. --- src/prototype/lang/object.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/prototype/lang/object.js b/src/prototype/lang/object.js index 6f8780cae..e59d9eb69 100644 --- a/src/prototype/lang/object.js +++ b/src/prototype/lang/object.js @@ -313,6 +313,12 @@ * prescribe an enumeration order. Sort the resulting array if you wish to * normalize the order of the object keys. * + * `Object.keys` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES5 specification](http://es5.github.com/#x15.2.3.14) for more + * information. + * * ##### Examples * * Object.keys(); From ecacc0246fab2e1234100f7df8cfaf6b59e30d2c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 5 Jun 2012 14:40:38 -0500 Subject: [PATCH 364/502] Bump version number. --- CHANGELOG | 5 +++++ src/constants.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 702a20b1e..796e90876 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +----------------------------------------------------------------------------- +NOTE: This changelog is no longer updated. Consult the commit history for a +list of changes from version to version. +----------------------------------------------------------------------------- + *1.7* (November 16, 2010) * Ensure `Element#update` works with string content that includes a LINK tag in Internet Explorer. [#264 state:resolved] (Tobias H. Michaelsen, Andrew Dupont) diff --git a/src/constants.yml b/src/constants.yml index e3db1baf3..c85abf6c6 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.7 +PROTOTYPE_VERSION: 1.7.1 From a3cac7cfe755a81f419395b7819eff1f56cd9607 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Wed, 27 Jun 2012 17:31:05 +0300 Subject: [PATCH 365/502] Unused var declaration and initialization is removed --- src/prototype/lang/function.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/function.js b/src/prototype/lang/function.js index 2d9d71b1f..f65938f9f 100644 --- a/src/prototype/lang/function.js +++ b/src/prototype/lang/function.js @@ -123,7 +123,7 @@ Object.extend(Function.prototype, (function() { var __method = this, args = slice.call(arguments, 1); var bound = function() { - var a = merge(args, arguments), c = context; + var a = merge(args, arguments); // Ignore the supplied context when the bound function is called with // the "new" keyword. var c = this instanceof bound ? this : context; From bc48c7ba3e06f78f372d79cb7b6180eb1c424076 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Wed, 27 Jun 2012 18:36:35 +0300 Subject: [PATCH 366/502] Removed `Element.extend` from `_element` accordingly to comment "use it internally as `_element` without having to extend the node". Restored `selector.match` in `findElement` because it may rely on `this`. Fixed typo in "IE-proprietarty". --- src/prototype/dom/event.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 4a161c444..d1ac7da69 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -244,10 +244,7 @@ // Fix a Safari bug where a text node gets passed as the target of an // anchor click rather than the anchor itself. - if (node.nodeType == Node.TEXT_NODE) - node = node.parentNode; - - return Element.extend(node); + return node.nodeType == Node.TEXT_NODE ? node.parentNode : node; } /** @@ -275,10 +272,10 @@ * }); **/ function findElement(event, expression) { - var element = _element(event), match = Prototype.Selector.match; + var element = _element(event), selector = Prototype.Selector; if (!expression) return Element.extend(element); while (element) { - if (Object.isElement(element) && match(element, expression)) + if (Object.isElement(element) && selector.match(element, expression)) return Element.extend(element); element = element.parentNode; } From b6897b44d0ba7b37ce7183b01b03f6c9211db689 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Fri, 13 Jul 2012 15:30:35 +0300 Subject: [PATCH 367/502] Wrong PDoc for Array#some and Array#every --- src/prototype/lang/array.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 464656860..2a5f0655f 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -647,16 +647,15 @@ Array.from = $A; } /** - * Array#some([iterator = Prototype.K[, context]]) -> Array + * Array#some([iterator = Prototype.K[, context]]) -> Boolean * - iterator (Function): An optional function to use to evaluate each * element in the enumeration; the function should return the value to * test. If this is not provided, the element itself is tested. * - context (Object): An optional object to use as `this` within * calls to the iterator. * - * Returns the result of applying `iterator` to each item in the array. If - * no iterator is provided, the elements are simply copied to the returned - * array. + * Determines whether at least one element is truthy (boolean-equivalent to + * `true`), either directly or through computation by the provided iterator. * * `Array#some` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). * It is only defined if not already present in the user's browser, and it @@ -692,7 +691,7 @@ Array.from = $A; * - context (Object): An optional object to use as `this` within * calls to the iterator. * - * Determines whether at least one element is truthy (boolean-equivalent to + * Determines whether all elements are truthy (boolean-equivalent to * `true`), either directly or through computation by the provided iterator. * * `Array#every` acts as an ECMAScript 5 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). From 6d62c44cf0da67363397b40c17ca27bd0c2d7193 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Thu, 9 Aug 2012 16:20:01 +0300 Subject: [PATCH 368/502] Fixed typo in PDoc --- src/prototype/ajax/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/ajax/request.js b/src/prototype/ajax/request.js index f50ece62f..b26424903 100644 --- a/src/prototype/ajax/request.js +++ b/src/prototype/ajax/request.js @@ -49,7 +49,7 @@ * status-specific callback is defined, it gets invoked. Otherwise, if * `onSuccess` is defined and the response is deemed a success (see below), it * is invoked. Otherwise, if `onFailure` is defined and the response is *not* - * deemed a sucess, it is invoked. Only after that potential first callback is + * deemed a success, it is invoked. Only after that potential first callback is * `onComplete` called. * * ##### A note on portability From 364ab46ed2a1d288204bc0c22772e410bde6ef5f Mon Sep 17 00:00:00 2001 From: Jochen Berger Date: Mon, 27 Aug 2012 09:26:20 +0200 Subject: [PATCH 369/502] add a test to ensure that the ObjectRange.each interator function in passed the index parameter --- test/unit/range_test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/unit/range_test.js b/test/unit/range_test.js index bcf5acb0b..c4618de1d 100644 --- a/test/unit/range_test.js +++ b/test/unit/range_test.js @@ -27,6 +27,13 @@ new Test.Unit.Runner({ }); this.assertEnumEqual([0, 1, 2, 3], results); + + results = []; + $R(2, 4, true).each(function(value, index) { + results.push(index); + }); + this.assertEnumEqual([0, 1], results); + }, testAny: function() { From 2729bcb4720f642ce70766a170ac89fdff398c9d Mon Sep 17 00:00:00 2001 From: Jochen Berger Date: Mon, 27 Aug 2012 10:23:26 +0200 Subject: [PATCH 370/502] fix index value not being passed to ObjectRange.each's iterator function --- src/prototype/lang/range.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/prototype/lang/range.js b/src/prototype/lang/range.js index d2679c552..931542f78 100644 --- a/src/prototype/lang/range.js +++ b/src/prototype/lang/range.js @@ -108,9 +108,9 @@ var ObjectRange = Class.create(Enumerable, (function() { } function _each(iterator, context) { - var value = this.start; - while (this.include(value)) { - iterator.call(context, value); + var value = this.start, i; + for (i = 0; this.include(value); i++) { + iterator.call(context, value, i); value = value.succ(); } } From 00470da692d436a8c92efc89e1d34eea991e4870 Mon Sep 17 00:00:00 2001 From: Jochen Berger Date: Mon, 27 Aug 2012 10:44:47 +0200 Subject: [PATCH 371/502] add a test to ensure that the Hash.each interator function in passed the index parameter --- test/unit/hash_test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/unit/hash_test.js b/test/unit/hash_test.js index 27df4e80a..d39f07840 100644 --- a/test/unit/hash_test.js +++ b/test/unit/hash_test.js @@ -182,6 +182,15 @@ new Test.Unit.Runner({ var foo = new FooMaker('bar'); this.assertEqual("key=bar", new Hash(foo).toQueryString()); this.assertEqual("key=bar", new Hash(new Hash(foo)).toQueryString()); + }, + + testIterationWithEach: function() { + var h = $H({a:1, b:2}); + var result = [] + h.each(function(kv, i){ + result.push(i); + }); + this.assertEnumEqual([0,1], result); } }); \ No newline at end of file From 8a0cd1e7b89e3e450c393eff7e4b5aea97db078f Mon Sep 17 00:00:00 2001 From: Jochen Berger Date: Mon, 27 Aug 2012 10:49:23 +0200 Subject: [PATCH 372/502] fix index value not being passed to Hash.each's iterator function --- src/prototype/lang/hash.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/prototype/lang/hash.js b/src/prototype/lang/hash.js index 7d6a232e1..d934327c0 100644 --- a/src/prototype/lang/hash.js +++ b/src/prototype/lang/hash.js @@ -94,11 +94,13 @@ var Hash = Class.create(Enumerable, (function() { // Our _internal_ each function _each(iterator, context) { + var i = 0; for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; - iterator.call(context, pair); + iterator.call(context, pair, i); + i++; } } From 08112b750381956a299b4054c106f37e57a7de45 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 27 Aug 2012 17:03:10 -0500 Subject: [PATCH 373/502] Fix PDoc errors. --- src/prototype/dom/event.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 4a161c444..88f27ceec 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -270,7 +270,7 @@ * * document.observe('click', function(event) { * var element = event.findElement('p'); - * if (element != document) + * if (element) * $(element).hide(); * }); **/ @@ -1276,7 +1276,7 @@ stopObserving: stopObserving.methodize(), /** - * Element.on(@element, eventName[, selector], callback) -> Event.Handler + * document.on(@element, eventName[, selector], callback) -> Event.Handler * * See [[Event.on]]. **/ From 6deb04975241186f404e0453fb29af54b069369b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 27 Aug 2012 17:06:10 -0500 Subject: [PATCH 374/502] Work around Opera issue in `createResponder`; simplify access to elements in `Event.cache`. --- src/prototype/dom/event.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 88f27ceec..c4373ae69 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -1331,9 +1331,9 @@ return createMouseEnterLeaveResponder(uid, eventName, handler); return function(event) { - var cacheEntry = Event.cache[uid]; - var element = cacheEntry.element; - + if (!Event.cache) return; + + var element = Event.cache[uid].element; Event.extend(event, element); handler.call(element, event); }; @@ -1341,7 +1341,7 @@ function createResponderForCustomEvent(uid, eventName, handler) { return function(event) { - var cacheEntry = Event.cache[uid], element = cacheEntry.element; + var element = Event.cache[uid].element; if (Object.isUndefined(event.eventName)) return false; @@ -1356,8 +1356,8 @@ function createMouseEnterLeaveResponder(uid, eventName, handler) { return function(event) { - var cacheEntry = Event.cache[uid], element = cacheEntry.element; - + var element = Event.cache[uid].element; + Event.extend(event, element); var parent = event.relatedTarget; From 2dfb3ae5244940211d14a86ac48df38d14d06e2d Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Tue, 28 Aug 2012 12:58:53 +0300 Subject: [PATCH 375/502] Short-circuit for Element#down() without arguments Prevent selecting ALL descendants with `Prototype.Selector.select('*', element)` when invoking Element#down() without arguments --- src/prototype/dom/dom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 5a02d0ef8..ee0a0a725 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1775,6 +1775,7 @@ * // -> undefined **/ function down(element, expression, index) { + if (arguments.length === 1) return firstDescendant(element); element = $(element), expression = expression || 0, index = index || 0; if (Object.isNumber(expression)) From ef4a928b46ff913428d009f40f0bbe3b1ecc22a0 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 31 Aug 2012 18:46:14 -0500 Subject: [PATCH 376/502] Optimize performance of `Event.stopObserving` by returning early when called on an element with no registered observers. [Victor Homyakov] --- src/prototype/dom/event.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 13f768234..3bef20bbb 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -894,14 +894,17 @@ // for bulk removal of event listeners. We use them rather than recurse // back into `stopObserving` to avoid touching the registry more often than // necessary. - + // Stop observing _all_ listeners on an element. function stopObservingElement(element) { - var uid = getUniqueElementID(element), - registry = getRegistryForElement(element, uid); - + // Do a manual registry lookup because we don't want to create a registry + // if one doesn't exist. + var uid = getUniqueElementID(element), registry = GLOBAL.Event.cache[uid]; + // This way we can return early if there is no registry. + if (!registry) return; + destroyRegistryForElement(element, uid); - + var entries, i; for (var eventName in registry) { // Explicitly skip elements so we don't accidentally find one with a From 1cd72a9687deaa77ea39295677c9cb2caa6611e2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 31 Aug 2012 19:33:14 -0500 Subject: [PATCH 377/502] Fix typo. --- src/prototype/lang/enumerable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/enumerable.js b/src/prototype/lang/enumerable.js index e6515e106..5f69925d5 100644 --- a/src/prototype/lang/enumerable.js +++ b/src/prototype/lang/enumerable.js @@ -5,7 +5,7 @@ * objects that act as collections of values. It is a cornerstone of * Prototype. * - * [[Enumerable]] is a _mixin_: a set of methods intended not for standaone + * [[Enumerable]] is a _mixin_: a set of methods intended not for standalone * use, but for incorporation into other objects. * * Prototype mixes [[Enumerable]] into several classes. The most visible cases From 4577941e335d52ee9a157c1c80731a818cc77bb6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 31 Aug 2012 19:54:54 -0500 Subject: [PATCH 378/502] Document the `$break` faux-keyword for `Enumerable` methods. --- src/prototype/lang/enumerable.js | 37 +++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/prototype/lang/enumerable.js b/src/prototype/lang/enumerable.js index 5f69925d5..d3287a174 100644 --- a/src/prototype/lang/enumerable.js +++ b/src/prototype/lang/enumerable.js @@ -30,7 +30,42 @@ * * If there is no `context` argument, the iterator function will execute in * the scope from which the [[Enumerable]] method itself was called. - * + * + * ##### Flow control + * + * You might find yourself missing the `break` and `continue` keywords that + * are available in ordinary `for` loops. If you need to break out of an + * enumeration before it's done, you can throw a special object named + * `$break`: + * + * var myObject = {}; + * + * ['foo', 'bar', 'baz', 'thud'].each( function(name, index) { + * if (name === 'baz') throw $break; + * myObject[name] = index; + * }); + * + * myObject; + * // -> { foo: 0, bar: 1 } + * + * Though we're technically throwing an exception, the `each` method knows + * to catch a thrown `$break` object and treat it as a command to stop + * iterating. (_Any_ exception thrown within an iterator will stop + * iteration, but only `$break` will be caught and suppressed.) + * + * If you need `continue`-like behavior, you can simply return early from + * your iterator: + * + * var myObject = {}; + * + * ['foo', 'bar', 'baz', 'thud'].each( function(name, index) { + * if (name === 'baz') return; + * myObject[name] = index; + * }); + * + * myObject; + * // -> { foo: 0, bar: 1, thud: 3 } + * * ##### Mixing [[Enumerable]] into your own objects * * So, let's say you've created your very own collection-like object (say, From addd725de13bcef84e31ae791cd81e98a1844a06 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 31 Aug 2012 21:39:31 -0500 Subject: [PATCH 379/502] Ensure we set `document.loaded` to `true` (and fire `dom:loaded`) when Prototype is loaded asynchronously after DOMContentLoaded. --- src/prototype/dom/event.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index 3bef20bbb..b9811a0ea 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -1407,6 +1407,15 @@ fireContentLoadedEvent(); } + + + if (document.readyState === 'complete') { + // We must have been loaded asynchronously, because the DOMContentLoaded + // event has already fired. We can just fire `dom:loaded` and be done + // with it. + fireContentLoadedEvent(); + return; + } if (document.addEventListener) { // All browsers that support DOM L2 Events support DOMContentLoaded, From 24414bfb1adb83616f7de09266d3eb190a533326 Mon Sep 17 00:00:00 2001 From: "P. Envall" Date: Thu, 20 Sep 2012 08:19:04 +0200 Subject: [PATCH 380/502] Add test and changes for serializing forms with multiple select to a string to work again. Signed-off-by: Jorgen Rydenius --- src/prototype/dom/form.js | 25 +++++++++++++++---------- test/unit/fixtures/form.html | 18 ++++++++++++++++++ test/unit/form_test.js | 5 +++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/prototype/dom/form.js b/src/prototype/dom/form.js index 8aa4b6e56..2ba5fa98a 100644 --- a/src/prototype/dom/form.js +++ b/src/prototype/dom/form.js @@ -119,16 +119,21 @@ var Form = { }; } else { initial = ''; - accumulator = function(result, key, value) { - // Normalize newlines as \r\n because the HTML spec says newlines should - // be encoded as CRLFs. - value = value.gsub(/(\r)?\n/, '\r\n'); - value = encodeURIComponent(value); - // Likewise, according to the spec, spaces should be '+' rather than - // '%20'. - value = value.gsub(/%20/, '+'); - return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + value; - } + accumulator = function(result, key, values) { + if (!Object.isArray(values)) {values = [values];} + if (!values.length) {return result;} + var encodedKey = encodeURIComponent(key); + return result + (result ? "&" : "") + values.map(function (value) { + // Normalize newlines as \r\n because the HTML spec says newlines should + // be encoded as CRLFs. + value = value.gsub(/(\r)?\n/, '\r\n'); + value = encodeURIComponent(value); + // Likewise, according to the spec, spaces should be '+' rather than + // '%20'. + value = value.gsub(/%20/, '+'); + return encodedKey + "=" + value; + }).join("&"); + }; } return elements.inject(initial, function(result, element) { diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index b0c0605d5..16c13d93a 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -125,3 +125,21 @@ + +
    + + + + +
    \ No newline at end of file diff --git a/test/unit/form_test.js b/test/unit/form_test.js index 84e0a10ab..21e72ad95 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -325,6 +325,11 @@ new Test.Unit.Runner({ this.assert(!select.anInputMethod); this.assertEqual('select', select.aSelectMethod()); }, + + testFormSerializeMultipleSelectToQueryString: function () { + var form = $("form_with_multiple_select"); + this.assertEqual("peewee=herman&colors=blue&colors=yellow&colors=not+grey&number=2", form.serialize(false)); + }, testFormRequest: function() { var request = $("form").request(); From e6c89d238d625642b641070b371c5d1aa335e264 Mon Sep 17 00:00:00 2001 From: Jorgen Rydenius Date: Thu, 20 Sep 2012 10:19:05 +0200 Subject: [PATCH 381/502] Fixed bug #1384 for the hash accumulator + test case. --- src/prototype/dom/form.js | 2 +- test/unit/fixtures/form.html | 3 ++- test/unit/form_test.js | 12 +++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/prototype/dom/form.js b/src/prototype/dom/form.js index 2ba5fa98a..8fd75ce6d 100644 --- a/src/prototype/dom/form.js +++ b/src/prototype/dom/form.js @@ -113,7 +113,7 @@ var Form = { accumulator = function(result, key, value) { if (key in result) { if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); + result[key] = result[key].concat(value); } else result[key] = value; return result; }; diff --git a/test/unit/fixtures/form.html b/test/unit/fixtures/form.html index 16c13d93a..727d9aa55 100644 --- a/test/unit/fixtures/form.html +++ b/test/unit/fixtures/form.html @@ -128,7 +128,8 @@
    - + +
    diff --git a/test/unit/form_test.js b/test/unit/form_test.js index 10c8844c2..52e834fe8 100644 --- a/test/unit/form_test.js +++ b/test/unit/form_test.js @@ -301,7 +301,7 @@ new Test.Unit.Runner({ }, testFormSerializeURIEncodesInputs: function() { - this.assertEqual("user%5Bwristbands%5D%5B%5D%5Bnickname%5D=H%C3%A4sslich", $('form_with_inputs_needing_encoding').serialize(false)); + this.assertEqual("user%5Bwristbands%5D%5B+%5D%5Bnickname%5D=H%C3%A4sslich", $('form_with_inputs_needing_encoding').serialize(false)); }, testFormMethodsOnExtendedElements: function() { From 554d512cc5bd61e2c2b5c233bc356945b199c411 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Mon, 24 Sep 2012 19:47:48 +0300 Subject: [PATCH 383/502] Prevent IE leaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without fix:  - when `ELEMENT_CACHE` is not empty (e.g. `SPAN` was created with `new Element('span'))`, IE will leak memory for `DIV` and `ELEMENT_CACHE` after page unload.  - when `ELEMENT_CACHE` is empty, `DIV` is cleaned correctly without any additional actions. Problem detected and fix tested in sIEve-0.0.8. --- src/prototype/dom/dom.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 8cc030dc9..579f7aba6 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3468,5 +3468,15 @@ } Element.addMethods(methods); - + + // Prevent IE leaks on DIV and ELEMENT_CACHE + DIV = null; + + function destroyCache_IE() { + ELEMENT_CACHE = null; + } + + if (window.attachEvent) + window.attachEvent('onunload', destroyCache_IE); + })(this); \ No newline at end of file From 37e6c1154d68b6333d3358ef69ecfb6d440476e2 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Tue, 25 Sep 2012 17:22:17 +0300 Subject: [PATCH 384/502] DIV cannot be cleared until page unload --- src/prototype/dom/dom.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 579f7aba6..2dc8acfd7 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3470,9 +3470,8 @@ Element.addMethods(methods); // Prevent IE leaks on DIV and ELEMENT_CACHE - DIV = null; - function destroyCache_IE() { + DIV = null; ELEMENT_CACHE = null; } From aa3983250f4f96d18acbc28938a78d5d01654d4c Mon Sep 17 00:00:00 2001 From: Jorgen Rydenius Date: Thu, 27 Sep 2012 14:13:14 +0200 Subject: [PATCH 385/502] Fix for #1339, regression that setOpacity IE regression in 1.7.1 when setting opacity on an element before it was added to DOM. Also fixed some related tests. --- src/prototype/dom/dom.js | 2 +- test/unit/dom_test.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 8cc030dc9..411484ab8 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2912,7 +2912,7 @@ } function hasLayout_IE(element) { - if (!element.currentStyle.hasLayout) + if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; return element; } diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 425261c42..b9ef36b6f 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -903,7 +903,10 @@ new Test.Unit.Runner({ this.assert( $('style_test_3').setOpacity(0.9999999).getStyle('opacity') > 0.999 ); - + + // setting opacity before element was added to DOM + this.assertEqual(0.5, new Element('div').setOpacity(0.5).getOpacity()); + /* IE <= 7 needs a `hasLayout` for opacity ("filter") to function properly @@ -927,10 +930,9 @@ new Test.Unit.Runner({ if (ZOOM_AFFECT_HAS_LAYOUT) { this.assert($('style_test_4').setOpacity(0.5).currentStyle.hasLayout); - this.assert(2, $('style_test_5').setOpacity(0.5).getStyle('zoom')); - this.assert(0.5, new Element('div').setOpacity(0.5).getOpacity()); - this.assert(2, new Element('div').setOpacity(0.5).setStyle('zoom: 2;').getStyle('zoom')); - this.assert(2, new Element('div').setStyle('zoom: 2;').setOpacity(0.5).getStyle('zoom')); + this.assertEqual(1, $('style_test_5').setOpacity(0.5).getStyle('zoom')); + this.assertEqual(2, new Element('div').setOpacity(0.5).setStyle('zoom: 2;').getStyle('zoom')); + this.assertEqual(2, new Element('div').setStyle('zoom: 2;').setOpacity(0.5).getStyle('zoom')); } }, From 7f9dc59b20c5bd575fabcb35cae542abe84c373c Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Wed, 3 Oct 2012 11:17:31 +0300 Subject: [PATCH 386/502] Fixed PDoc for toQueryString() --- src/prototype/lang/object.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/object.js b/src/prototype/lang/object.js index e59d9eb69..675530a74 100644 --- a/src/prototype/lang/object.js +++ b/src/prototype/lang/object.js @@ -244,7 +244,7 @@ * ##### Examples * * Object.toQueryString({ action: 'ship', order_id: 123, fees: ['f1', 'f2'], 'label': 'a demo' }) - * // -> 'action=ship&order_id=123&fees=f1&fees=f2&label=a%20demo' + * // -> 'action=ship&order_id=123&fees=f1&fees=f2&label=a+demo' **/ function toQueryString(object) { return $H(object).toQueryString(); From 6336c456d85267dbd50408eea8a1676df9849391 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Mon, 22 Oct 2012 19:13:22 +0300 Subject: [PATCH 387/502] `toQueryPair()`: `value` is already defined --- src/prototype/lang/hash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/lang/hash.js b/src/prototype/lang/hash.js index d934327c0..662640fcf 100644 --- a/src/prototype/lang/hash.js +++ b/src/prototype/lang/hash.js @@ -291,7 +291,7 @@ var Hash = Class.create(Enumerable, (function() { function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; - var value = String.interpret(value); + value = String.interpret(value); // Normalize newlines as \r\n because the HTML spec says newlines should // be encoded as CRLFs. From 3edad4f223f30fa3a3f912e78381dd9318a17846 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Tue, 23 Oct 2012 12:52:08 +0300 Subject: [PATCH 388/502] Merged nested if() into single expression --- src/prototype/lang/enumerable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype/lang/enumerable.js b/src/prototype/lang/enumerable.js index d3287a174..f55eac347 100644 --- a/src/prototype/lang/enumerable.js +++ b/src/prototype/lang/enumerable.js @@ -402,8 +402,8 @@ var Enumerable = (function() { * // -> true ('3' == 3) **/ function include(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; + if (Object.isFunction(this.indexOf) && this.indexOf(object) != -1) + return true; var found = false; this.each(function(value) { From 4a22a52e08fce7dfe95bf54b8f52314614ee24b3 Mon Sep 17 00:00:00 2001 From: Sergiu Dumitriu Date: Thu, 25 Oct 2012 20:53:26 -0400 Subject: [PATCH 389/502] Issue #1431: writeAttribute('checked') fails to write the 'checked' attribute, writes an 'undefined' attribute instead --- src/prototype/dom/dom.js | 2 +- test/unit/dom_test.js | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index acc42dbeb..ea6fc1332 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2353,7 +2353,7 @@ name = table.names[attr] || attr; value = attributes[attr]; if (table.values[attr]) - name = table.values[attr](element, value); + name = table.values[attr](element, value) || name; if (value === false || value === null) element.removeAttribute(name); else if (value === true) diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index b9ef36b6f..a277947c9 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1090,9 +1090,7 @@ new Test.Unit.Runner({ testElementWriteAttributeWithBooleans: function() { var input = $('write_attribute_input'), - select = $('write_attribute_select'), - checkbox = $('write_attribute_checkbox'), - checkedCheckbox = $('write_attribute_checked_checkbox'); + select = $('write_attribute_select'); this.assert( input. writeAttribute('readonly'). hasAttribute('readonly')); this.assert(!input. writeAttribute('readonly', false). hasAttribute('readonly')); this.assert( input. writeAttribute('readonly', true). hasAttribute('readonly')); @@ -1100,8 +1098,28 @@ new Test.Unit.Runner({ this.assert( input. writeAttribute('readonly', 'readonly').hasAttribute('readonly')); this.assert( select. writeAttribute('multiple'). hasAttribute('multiple')); this.assert( input. writeAttribute('disabled'). hasAttribute('disabled')); + }, + testElementWriteAttributeForCheckbox: function() { + var checkbox = $('write_attribute_checkbox'), + checkedCheckbox = $('write_attribute_checked_checkbox'); this.assert( checkbox. writeAttribute('checked'). checked); + this.assert( checkbox. writeAttribute('checked'). hasAttribute('checked')); + this.assertEqual('checked', checkbox.writeAttribute('checked').getAttribute('checked')); + this.assert(!checkbox. writeAttribute('checked'). hasAttribute('undefined')); + this.assert( checkbox. writeAttribute('checked', true). checked); + this.assert( checkbox. writeAttribute('checked', true). hasAttribute('checked')); + this.assert( checkbox. writeAttribute('checked', 'checked'). checked); + this.assert( checkbox. writeAttribute('checked', 'checked'). hasAttribute('checked')); + this.assert(!checkbox. writeAttribute('checked', null). checked); + this.assert(!checkbox. writeAttribute('checked', null). hasAttribute('checked')); + this.assert(!checkbox. writeAttribute('checked', true). hasAttribute('undefined')); this.assert(!checkedCheckbox.writeAttribute('checked', false). checked); + this.assert(!checkbox. writeAttribute('checked', false). hasAttribute('checked')); + }, + testElementWriteAttributeForStyle: function() { + var element = Element.extend(document.body.appendChild(document.createElement('p'))); + this.assert( element. writeAttribute('style', 'color: red'). hasAttribute('style')); + this.assert(!element. writeAttribute('style', 'color: red'). hasAttribute('undefined')); }, testElementWriteAttributeWithIssues: function() { From a8f0434fa13f785ff2a360feb5b8356ec47ecf47 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Tue, 25 Dec 2012 21:40:58 +0300 Subject: [PATCH 390/502] viewportOffset: extend forElement (it's used twice) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `forElement` was used twice in `viewportOffset`: 1. at line 1147 as `var element = $(forElement);` 2. at line 1156 as `element = forElement;` This leads to error when `forElement` is string: `Element.viewportOffset('id')` --- src/prototype/dom/layout.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index 8b6757591..04d517e58 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1144,7 +1144,8 @@ function viewportOffset(forElement) { var valueT = 0, valueL = 0, docBody = document.body; - var element = $(forElement); + forElement = $(forElement); + var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; From 2811f40a464b69274f407a3e39ce0e08584f39bd Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Thu, 27 Dec 2012 13:36:16 +0300 Subject: [PATCH 391/502] Possibility to remove X-* headers for CORS request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `setRequestHeaders()` unconditionally adds specific headers to any request. This patch gives possibility to remove these headers in order to allow simple CORS request. Based on http://stackoverflow.com/questions/13814739/prototype-ajax-request-being-sent-as-options-rather-than-get-results-in-501-err and https://prototype.lighthouseapp.com/projects/8886/tickets/1590-ability-to-remove-headers-in-ajaxrequestsetrequestheaders-for-cors --- src/prototype/ajax/request.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/prototype/ajax/request.js b/src/prototype/ajax/request.js index b26424903..9bcd689ba 100644 --- a/src/prototype/ajax/request.js +++ b/src/prototype/ajax/request.js @@ -259,8 +259,10 @@ Ajax.Request = Class.create(Ajax.Base, { $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } + // skip null or undefined values for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); + if (headers[name] != null) + this.transport.setRequestHeader(name, headers[name]); }, /** From 30b11b7cc048bb4f1778e9a27d7d90f3cdd00bf5 Mon Sep 17 00:00:00 2001 From: Jason Westbrook Date: Fri, 28 Dec 2012 13:53:16 -0800 Subject: [PATCH 392/502] IE8 fix for the opacity check IE8 fails to set the match variable which throws a javascript variable --- src/prototype/dom/dom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index ea6fc1332..f14639bd9 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3000,7 +3000,7 @@ var filter = Element.getStyle(element, 'filter'); if (filter.length === 0) return 1.0; var match = (filter || '').match(/alpha\(opacity=(.*)\)/); - if (match[1]) return parseFloat(match[1]) / 100; + if (match && match[1]) return parseFloat(match[1]) / 100; return 1.0; } @@ -3478,4 +3478,4 @@ if (window.attachEvent) window.attachEvent('onunload', destroyCache_IE); -})(this); \ No newline at end of file +})(this); From fa1f8993f23a9fb51314fd18b50698a0fef9902b Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Thu, 24 Jan 2013 22:12:20 +0300 Subject: [PATCH 393/502] Firefox 18+ supports String#startsWith, String#endsWith --- src/prototype/lang/string.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 558db3280..433062ea9 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -878,8 +878,9 @@ Object.extend(String.prototype, (function() { isJSON: isJSON, evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, include: include, - startsWith: startsWith, - endsWith: endsWith, + // Firefox 18+ supports String.prototype.startsWith, String.prototype.endsWith + startsWith: String.prototype.startsWith || startsWith, + endsWith: String.prototype.endsWith || endsWith, empty: empty, blank: blank, interpolate: interpolate From e06ad15a3bab8267ce5413a7ba93cfcada34bf0e Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Sun, 27 Jan 2013 01:48:08 +0300 Subject: [PATCH 394/502] Optional position argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added optional position argument to `String#startsWith`, `String#endsWith`, as described in https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/startsWith and https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/endsWith. Documented new arguments and examples. --- src/prototype/lang/string.js | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 433062ea9..c62e8964d 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -770,7 +770,9 @@ Object.extend(String.prototype, (function() { } /** - * String#startsWith(substring) -> Boolean + * String#startsWith(substring[, position]) -> Boolean + * - substring (String): The characters to be searched for at the start of this string. + * - [position] (Number): The position in this string at which to begin searching for `substring`; defaults to 0. * * Checks if the string starts with `substring`. * @@ -778,15 +780,21 @@ Object.extend(String.prototype, (function() { * * 'Prototype JavaScript'.startsWith('Pro'); * //-> true + * 'Prototype JavaScript'.startsWith('Java', 10); + * //-> true **/ - function startsWith(pattern) { + function startsWith(pattern, position) { + position = Object.isNumber(position) ? position : 0; // We use `lastIndexOf` instead of `indexOf` to avoid tying execution // time to string length when string doesn't start with pattern. - return this.lastIndexOf(pattern, 0) === 0; + return this.lastIndexOf(pattern, position) === position; } /** - * String#endsWith(substring) -> Boolean + * String#endsWith(substring[, position]) -> Boolean + * - substring (String): The characters to be searched for at the end of this string. + * - [position] (Number): Search within this string as if this string were only this long; + * defaults to this string's actual length, clamped within the range established by this string's length. * * Checks if the string ends with `substring`. * @@ -794,9 +802,15 @@ Object.extend(String.prototype, (function() { * * 'slaughter'.endsWith('laughter') * // -> true + * 'slaughter'.endsWith('laugh', 6) + * // -> true **/ - function endsWith(pattern) { - var d = this.length - pattern.length; + function endsWith(pattern, position) { + pattern = String(pattern); + position = Object.isNumber(position) ? position : this.length; + if (position < 0) position = 0; + if (position > this.length) position = this.length; + var d = position - pattern.length; // We use `indexOf` instead of `lastIndexOf` to avoid tying execution // time to string length when string doesn't end with pattern. return d >= 0 && this.indexOf(pattern, d) === d; From bc3ac95c43c200033697d66012cfc7ac0d95e9b1 Mon Sep 17 00:00:00 2001 From: Victor Homyakov Date: Sun, 27 Jan 2013 01:54:39 +0300 Subject: [PATCH 395/502] Updated unit tests --- test/unit/string_test.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 5b274b2a2..b59ab2db1 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -430,6 +430,11 @@ new Test.Unit.Runner({ this.assert(!'hello world'.startsWith('bye')); this.assert(!''.startsWith('bye')); this.assert(!'hell'.startsWith('hello')); + + var str = "To be, or not to be, that is the question"; + this.assert(str.startsWith("To be"), 'str.startsWith("To be")'); + this.assert(!str.startsWith("not to be"), 'str.startsWith("not to be")'); + this.assert(str.startsWith("not to be", 10), 'str.startsWith("not to be", 10)'); }, testEndsWith: function() { @@ -439,6 +444,25 @@ new Test.Unit.Runner({ this.assert(!''.endsWith('planet')); this.assert('hello world world'.endsWith(' world')); this.assert(!'z'.endsWith('az')); + + var str = "To be, or not to be, that is the question"; + this.assert(str.endsWith("question"), 'str.endsWith("question")'); + this.assert(!str.endsWith("to be"), 'str.endsWith("to be")'); + this.assert(str.endsWith("to be", 19), 'str.endsWith("to be", 19)'); + + str = "12345"; + this.assert(str.endsWith("5")); + this.assert(str.endsWith("5", 6)); + this.assert(str.endsWith("5", 5)); + this.assert(!str.endsWith("5", 4)); + this.assert(!str.endsWith("5", 1)); + this.assert(!str.endsWith("5", 0)); + + this.assert(str.endsWith("1", 1)); + this.assert(!str.endsWith("1", 0)); + this.assert(!str.endsWith("1", -1)); + + this.assert(str.endsWith("", 0)); }, testBlank: function() { @@ -551,4 +575,4 @@ new Test.Unit.Runner({ this.assertIdentical(false, 'false'.evalJSON()); this.assertEqual('"', '"\\""'.evalJSON()); } -}); \ No newline at end of file +}); From f2a1136992a7ade837ce6c3bf43eec4381793a7a Mon Sep 17 00:00:00 2001 From: Kir Maximov Date: Sun, 3 Feb 2013 15:56:49 +0100 Subject: [PATCH 396/502] Bugfix in toQueryParams (didn't decode '+' correctly) --- src/prototype/lang/string.js | 7 +++++-- test/unit/hash_test.js | 1 + test/unit/string_test.js | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 558db3280..5a4a8f685 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -473,8 +473,11 @@ Object.extend(String.prototype, (function() { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()), value = pair.length > 1 ? pair.join('=') : pair[0]; - - if (value != undefined) value = decodeURIComponent(value); + + if (value != undefined) { + value = value.gsub('+', ' '); + value = decodeURIComponent(value); + } if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; diff --git a/test/unit/hash_test.js b/test/unit/hash_test.js index d39f07840..ed779f2d5 100644 --- a/test/unit/hash_test.js +++ b/test/unit/hash_test.js @@ -120,6 +120,7 @@ new Test.Unit.Runner({ this.assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString()); this.assertEqual("a=b&c", $H(Fixtures.value_undefined).toQueryString()); this.assertEqual("a=b&c", $H("a=b&c".toQueryParams()).toQueryString()); + this.assertEqual("a=b+d&c", $H("a=b+d&c".toQueryParams()).toQueryString()); this.assertEqual("a=b&c=", $H(Fixtures.value_null).toQueryString()); this.assertEqual("a=b&c=0", $H(Fixtures.value_zero).toQueryString()); this.assertEqual("color=r&color=g&color=b", $H(Fixtures.multiple).toQueryString()); diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 5b274b2a2..3ec94a5c9 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -390,7 +390,8 @@ new Test.Unit.Runner({ this.assertHashEqual({a:undefined}, 'a'.toQueryParams(), 'key without value'); this.assertHashEqual({a:'b'}, 'a=b&=c'.toQueryParams(), 'empty key'); this.assertHashEqual({a:'b', c:''}, 'a=b&c='.toQueryParams(), 'empty value'); - + this.assertHashEqual({a:' '}, 'a=++'.toQueryParams(), 'value of spaces'); + this.assertHashEqual({'a b':'c', d:'e f', g:'h'}, 'a%20b=c&d=e%20f&g=h'.toQueryParams(), 'proper decoding'); this.assertHashEqual({a:'b=c=d'}, 'a=b=c=d'.toQueryParams(), 'multiple equal signs'); From beb5dbc48c29558cac2f8267ed3ad8fba1ed9059 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 26 Mar 2013 00:15:48 -0500 Subject: [PATCH 397/502] Add documentation example for `Element#clone.` --- src/prototype/dom/dom.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 5a02d0ef8..59546ffc6 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -1145,6 +1145,24 @@ * * A wrapper around DOM Level 2 `Node#cloneNode`, [[Element.clone]] cleans up * any expando properties defined by Prototype. + * + * ##### Example + * + *
    + *
    + *
    + * + * var clone = $('original').clone(); + * clone.className; + * // -> "original" + * clone.childElements(); + * // -> [] + * + * var deepClone = $('original').clone(true); + * deepClone.className; + * // -> "original" + * deepClone.childElements(); + * // -> [div.original_child] **/ function clone(element, deep) { if (!(element = $(element))) return; From b07fd9469845dab442dc19f41ee9978b7bb3961b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Tue, 26 Mar 2013 00:32:05 -0500 Subject: [PATCH 398/502] Add polyfill information to documentation for `String#startsWith` and `String#endsWith`. --- src/prototype/lang/string.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index c62e8964d..94fc99702 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -771,11 +771,19 @@ Object.extend(String.prototype, (function() { /** * String#startsWith(substring[, position]) -> Boolean - * - substring (String): The characters to be searched for at the start of this string. - * - [position] (Number): The position in this string at which to begin searching for `substring`; defaults to 0. + * - substring (String): The characters to be searched for at the start of + * this string. + * - [position] (Number): The position in this string at which to begin + * searching for `substring`; defaults to 0. * * Checks if the string starts with `substring`. * + * `String#startsWith` acts as an ECMAScript 6 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES6 specification](http://wiki.ecmascript.org/doku.php?id=harmony%3Aspecification_drafts) for more + * information. + * * ##### Example * * 'Prototype JavaScript'.startsWith('Pro'); @@ -792,12 +800,20 @@ Object.extend(String.prototype, (function() { /** * String#endsWith(substring[, position]) -> Boolean - * - substring (String): The characters to be searched for at the end of this string. - * - [position] (Number): Search within this string as if this string were only this long; - * defaults to this string's actual length, clamped within the range established by this string's length. + * - substring (String): The characters to be searched for at the end of + * this string. + * - [position] (Number): Search within this string as if this string were + * only this long; defaults to this string's actual length, clamped + * within the range established by this string's length. * * Checks if the string ends with `substring`. * + * `String#endsWith` acts as an ECMAScript 6 [polyfill](http://remysharp.com/2010/10/08/what-is-a-polyfill/). + * It is only defined if not already present in the user's browser, and it + * is meant to behave like the native version as much as possible. Consult + * the [ES6 specification](http://wiki.ecmascript.org/doku.php?id=harmony%3Aspecification_drafts) for more + * information. + * * ##### Example * * 'slaughter'.endsWith('laughter') From faa0ba932ed9d62a5476a6e62d87069e5ef884b4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 4 Apr 2013 18:58:53 -0500 Subject: [PATCH 399/502] Fix an issue with a capability check causing an error in IE 10. --- src/prototype/dom/dom.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 7b72941c5..f97ac77ec 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2336,9 +2336,12 @@ } var PROBLEMATIC_ATTRIBUTE_READING = (function() { - DIV.setAttribute('onclick', Prototype.emptyFunction); + // This test used to set 'onclick' to `Prototype.emptyFunction`, but that + // caused an (uncatchable) error in IE 10. For some reason, switching to + // an empty array prevents this issue. + DIV.setAttribute('onclick', []); var value = DIV.getAttribute('onclick'); - var isFunction = (typeof value === 'function'); + var isFunction = Object.isArray(value); DIV.removeAttribute('onclick'); return isFunction; })(); From e9c18bcd7dcb500ddf515aa1b711e3e81d94288b Mon Sep 17 00:00:00 2001 From: "Kirill (KIR) Maximov" Date: Fri, 19 Apr 2013 12:54:34 +0200 Subject: [PATCH 400/502] Memory leak fix in Prototype. --- src/prototype/dom/event.js | 13 ++++++++++++- test/unit/event_test.js | 25 ++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index b9811a0ea..fd6864eeb 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -593,7 +593,10 @@ // Remove the entry from the collection; var index = entries.indexOf(entry); entries.splice(index, 1); - + + if (entries.length == 0) { + stopObservingEventName(element, eventName); + } return entry; } @@ -928,6 +931,14 @@ var i = entries.length; while (i--) removeEvent(element, eventName, entries[i].responder); + + for (var name in registry) { + if (name === 'element') continue; + return; // There is another registered event + } + + // No other events for the element, destroy the registry: + destroyRegistryForElement(element); } diff --git a/test/unit/event_test.js b/test/unit/event_test.js index de274cb3b..27919a880 100644 --- a/test/unit/event_test.js +++ b/test/unit/event_test.js @@ -159,7 +159,8 @@ new Test.Unit.Runner({ var span = $("span"), observer = Prototype.emptyFunction, eventID; span.observe("test:somethingHappened", observer); - + span.observe("test:somethingHappened", function() {}); + function uidForElement(elem) { return elem.uniqueID ? elem.uniqueID : elem._prototypeUID; } @@ -168,7 +169,7 @@ new Test.Unit.Runner({ this.assert(registry, 'registry should exist'); this.assert(Object.isArray(registry['test:somethingHappened'])); - this.assertEqual(1, registry['test:somethingHappened'].length); + this.assertEqual(2, registry['test:somethingHappened'].length); span.stopObserving("test:somethingHappened", observer); @@ -176,9 +177,27 @@ new Test.Unit.Runner({ this.assert(registry); this.assert(Object.isArray(registry['test:somethingHappened'])); - this.assertEqual(0, registry['test:somethingHappened'].length); + this.assertEqual(1, registry['test:somethingHappened'].length); }, + testLastStopObservingClearesCache: function() { + var span = $("span"), observer = Prototype.emptyFunction, eventID; + delete Event.cache[uidForElement(span)]; + + span.observe("test:somethingHappened", observer); + + function uidForElement(elem) { + return elem.uniqueID ? elem.uniqueID : elem._prototypeUID; + } + + span.stopObserving("test:somethingHappened", observer); + + var registry = Event.cache[uidForElement(span)]; + + this.assert(!registry); +// console.info(registry) + }, + testObserveAndStopObservingAreChainable: function() { var span = $("span"), observer = Prototype.emptyFunction; From b840174fe1e2b23ea5eb3c39f388553e8e4a241a Mon Sep 17 00:00:00 2001 From: superdav42 Date: Wed, 16 Oct 2013 00:05:18 -0600 Subject: [PATCH 401/502] Add space to separate existing filters from alpha filter In IE <= 8 it was impossible to setOpacity on an element that already had an existing filter such as filter: FlipH; The filters need to be space seperated --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index f97ac77ec..55531f6ae 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2998,7 +2998,7 @@ if (value < 0.00001) value = 0; style.filter = stripAlphaFromFilter_IE(filter) + - 'alpha(opacity=' + (value * 100) + ')'; + ' alpha(opacity=' + (value * 100) + ')'; return element; } From 0d718c5a39bdadd83c698cb97d4891adf08f7c1d Mon Sep 17 00:00:00 2001 From: Jason Westbrook Date: Wed, 30 Oct 2013 21:01:28 -0700 Subject: [PATCH 402/502] Added fix based on https://prototype.lighthouseapp.com/projects/8886/tickets/3701 --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index f97ac77ec..2980b2b35 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3020,7 +3020,7 @@ var filter = Element.getStyle(element, 'filter'); if (filter.length === 0) return 1.0; - var match = (filter || '').match(/alpha\(opacity=(.*)\)/); + var match = (filter || '').match(/alpha\(opacity=(.*)\)/i); if (match && match[1]) return parseFloat(match[1]) / 100; return 1.0; } From 6e8094ee0573e34a57775728131eb05fca120ed9 Mon Sep 17 00:00:00 2001 From: Jason Westbrook Date: Fri, 17 Jan 2014 22:45:03 -0800 Subject: [PATCH 403/502] Fixes scollTop property that is deprecated on body Chrome 32 deprecates document.body.scrollTop, should use document.documentElement.scrollTop instead. Polyfill describe here https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY --- src/prototype/dom/layout.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index 04d517e58..d4f7bf404 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1129,9 +1129,15 @@ function cumulativeScrollOffset(element) { var valueT = 0, valueL = 0; do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; + if(element == document.body){ + valueT += (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop || 0; + valueL += (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft || 0; + break; + } else { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } } while (element); return new Element.Offset(valueL, valueT); } From f4cb26c838f755c772366f1066c53a3605abf001 Mon Sep 17 00:00:00 2001 From: Jason Westbrook Date: Sat, 18 Jan 2014 17:04:02 -0800 Subject: [PATCH 404/502] Style and reduce redundant code --- src/prototype/dom/layout.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index d4f7bf404..e389f8aa8 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1129,9 +1129,10 @@ function cumulativeScrollOffset(element) { var valueT = 0, valueL = 0; do { - if(element == document.body){ - valueT += (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop || 0; - valueL += (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft || 0; + if (element === document.body) { + var bodyScrollNode = document.documentElement || document.body.parentNode || document.body; + valueT += !Object.isUndefined(window.pageYOffset) ? window.pageYOffset : bodyScrollNode.scrollTop || 0; + valueL += !Object.isUndefined(window.pageXOffset) ? window.pageXOffset : bodyScrollNode.scrollLeft || 0; break; } else { valueT += element.scrollTop || 0; From 810f0f609df2499034391165da94065afce7c91a Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 5 Feb 2014 09:47:57 -0600 Subject: [PATCH 405/502] Fix failing string test in Chrome. --- src/prototype/lang/string.js | 11 +++++++++-- test/unit/string_test.js | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/prototype/lang/string.js b/src/prototype/lang/string.js index 1e0277ca4..736fdb2e2 100644 --- a/src/prototype/lang/string.js +++ b/src/prototype/lang/string.js @@ -38,6 +38,13 @@ Object.extend(String.prototype, (function() { var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } + + // In some versions of Chrome, an empty RegExp has "(?:)" as a `source` + // property instead of an empty string. + function isNonEmptyRegExp(regexp) { + return regexp.source && regexp.source !== '(?:)'; + } + /** * String#gsub(pattern, replacement) -> String @@ -93,8 +100,8 @@ Object.extend(String.prototype, (function() { if (Object.isString(pattern)) pattern = RegExp.escape(pattern); - - if (!(pattern.length || pattern.source)) { + + if (!(pattern.length || isNonEmptyRegExp(pattern))) { replacement = replacement(''); return replacement + source.split('').join(replacement) + replacement; } diff --git a/test/unit/string_test.js b/test/unit/string_test.js index 496e456b5..b000ad6cd 100644 --- a/test/unit/string_test.js +++ b/test/unit/string_test.js @@ -46,9 +46,9 @@ new Test.Unit.Runner({ 'ウィメンズ2007\nクルーズコレクション'.gsub('\n','
    ')); this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar', - source.gsub('', 'bar')); + source.gsub('', 'bar'), 'empty string'); this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar', - source.gsub(new RegExp(''), 'bar')); + source.gsub(new RegExp(''), 'bar'), 'empty regexp'); }, testGsubWithReplacementTemplateString: function() { From a9ecf6548e5ac577b4ee40b85ae78d1011f96206 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 18:17:08 -0500 Subject: [PATCH 406/502] Update Sizzle to latest stable version. --- vendor/sizzle/repository | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sizzle/repository b/vendor/sizzle/repository index 3ba396e43..a9eb3ca3c 160000 --- a/vendor/sizzle/repository +++ b/vendor/sizzle/repository @@ -1 +1 @@ -Subproject commit 3ba396e439a07c2a2facafbe07cdaa1b80a24c00 +Subproject commit a9eb3ca3c5e1b568057390f73da385809ac69340 From cb0257548788a250191a658717c1184c2f31430f Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 18:31:44 -0500 Subject: [PATCH 407/502] Update include point for latest version of Sizzle. --- vendor/sizzle/selector_engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index 8100dd59e..fd44958d6 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_property = window.Sizzle; -//= require "repository/sizzle" +//= require "repository/src/sizzle" ;(function(engine) { var extendElements = Prototype.Selector.extendElements; From 21547990c5675bd44671eeffcab0f523e47b6db2 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 18:32:31 -0500 Subject: [PATCH 408/502] Remove a failing test. (Sizzle doesn't seem to support this anymore, which is fine with me because it's a ridiculous selector.) --- test/unit/selector_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index 074bcdfcb..e81c84045 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -103,7 +103,6 @@ new Test.Unit.Runner({ this.assertEnumEqual($('chk_1', 'chk_2'), $$('#troubleForm2 input[name="brackets[5][]"]')); this.assertEnumEqual([$('chk_1')], $$('#troubleForm2 input[name="brackets[5][]"]:checked')); this.assertEnumEqual([$('chk_2')], $$('#troubleForm2 input[name="brackets[5][]"][value=2]')); - this.assertEnumEqual([], $$('#troubleForm2 input[name=brackets[5][]]')); }, test$$WithNestedAttributeSelectors: function() { From 9d49b1bb4a6e85dd268e69dbf0f918900b449721 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 18:37:02 -0500 Subject: [PATCH 409/502] Change failing tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of these tests presumably tests comma handling — whether we do the right thing when the space is before the comma, or when there end up being duplicate commas — and this is not something we handled before, so if this is a newly-failing test it's because Sizzle used to handle this and now does not. That's fine with me. The other one appears to be a disagreement over whether `:enabled` should match `input[type=hidden]`, and Sizzle apparently says it does, and I'm OK with that, too. --- test/unit/selector_test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index e81c84045..ccb8a9147 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -285,7 +285,7 @@ new Test.Unit.Runner({ testSelectorWithEnabledDisabledChecked: function() { this.assertEnumEqual([$('disabled_text_field')], $$('#troubleForm > *:disabled'), ':disabled'); - this.assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field'), $('hidden')), $$('#troubleForm > *:enabled'), ':enabled'); + this.assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field')), $$('#troubleForm > *:enabled'), ':enabled'); this.assertEnumEqual($('checked_box', 'checked_radio'), $$('#troubleForm *:checked'), ':checked'); }, @@ -319,8 +319,8 @@ new Test.Unit.Runner({ }, testCommasFor$$: function() { - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,#item_3 , #troubleForm')); - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,', '#item_3 , #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first, #item_3, #troubleForm')); + this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first', '#item_3, #troubleForm')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]')); this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]')); }, From 84a4d4fa2d868d5a0e08d683a4bb6bf661e816b5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 19:52:58 -0500 Subject: [PATCH 410/502] Fix issue in old versions of IE with checkbox reading. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the tests added in 4a22a5 apparently never worked in old IE. I added a capability check for the buggy behavior and a new path for IE. Also, changed a `getAttribute` in a test to `readAttribute` — otherwise the test would never pass in IE 6-7, since `getAttribute` doesn't work right. --- src/prototype/dom/dom.js | 25 ++++++++++++++++++++++++- test/unit/dom_test.js | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index f97ac77ec..239f61d07 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2385,13 +2385,36 @@ return element; } + // Test whether checkboxes work properly with `hasAttribute`. + var PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES = (function () { + if (!HAS_EXTENDED_CREATE_ELEMENT_SYNTAX) { + // Only IE browsers are known to exhibit this one, so we'll take a + // shortcut. + return false; + } + var checkbox = document.createElement(''); + checkbox.checked = true; + var node = checkbox.getAttributeNode('checked'); + var buggy = !node.specified; + return !node.specified; + })(); + function hasAttribute(element, attribute) { attribute = ATTRIBUTE_TRANSLATIONS.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return !!(node && node.specified); } - GLOBAL.Element.Methods.Simulated.hasAttribute = hasAttribute; + function hasAttribute_IE(element, attribute) { + if (attribute === 'checked') { + return element.checked; + } + return hasAttribute(element, attribute); + } + + GLOBAL.Element.Methods.Simulated.hasAttribute = + PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES ? + hasAttribute_IE : hasAttribute; /** deprecated * Element.classNames(@element) -> [String...] diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index a277947c9..54868213b 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1104,7 +1104,7 @@ new Test.Unit.Runner({ checkedCheckbox = $('write_attribute_checked_checkbox'); this.assert( checkbox. writeAttribute('checked'). checked); this.assert( checkbox. writeAttribute('checked'). hasAttribute('checked')); - this.assertEqual('checked', checkbox.writeAttribute('checked').getAttribute('checked')); + this.assertEqual('checked', checkbox.writeAttribute('checked').readAttribute('checked')); this.assert(!checkbox. writeAttribute('checked'). hasAttribute('undefined')); this.assert( checkbox. writeAttribute('checked', true). checked); this.assert( checkbox. writeAttribute('checked', true). hasAttribute('checked')); From 0df1298f807818471209b7e7971c3480e36a4f71 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 27 Mar 2014 19:57:46 -0500 Subject: [PATCH 411/502] Add a `test:server` rake task. For IE, I find it far easier to start the test server on my dev machine, then use my IE machine to visit the test pages manually. For that I need a task that runs the server indefinitely. At some point I'll add a page that does nothing but list the tests for convenience. --- Rakefile | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Rakefile b/Rakefile index aeab1959b..e6f661481 100755 --- a/Rakefile +++ b/Rakefile @@ -305,6 +305,32 @@ namespace :test do task :require do PrototypeHelper.require_unittest_js end + + desc "Builds all the unit tests and starts the server. (The user can visit the tests manually in a browser at their leisure.)" + task :server => [:build] do + runner = UnittestJS::WEBrickRunner::Runner.new(:test_dir => PrototypeHelper::TMP_DIR) + testcases = ENV['TESTCASES'] + + Dir[File.join(PrototypeHelper::TMP_DIR, '*_test.html')].each do |file| + file = File.basename(file) + test = file.sub('_test.html', '') + runner.add_test(file, testcases) + end + + trap('INT') do + puts "...server stopped." + runner.teardown + exit + end + + puts "Server started..." + + runner.setup + + loop do + sleep 1 + end + end end task :test_units do From e87f4b7fec616d24b15f16c1565d03dc59a8e8ba Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 6 Apr 2014 20:16:08 -0500 Subject: [PATCH 412/502] Fix a capability check that raised an exception in IE8. --- src/prototype/dom/dom.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 239f61d07..eee4fddd7 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2395,8 +2395,7 @@ var checkbox = document.createElement(''); checkbox.checked = true; var node = checkbox.getAttributeNode('checked'); - var buggy = !node.specified; - return !node.specified; + return !node || !node.specified; })(); function hasAttribute(element, attribute) { From 3523295165460a1a371f248454bc311103294f13 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 6 Apr 2014 20:16:57 -0500 Subject: [PATCH 413/502] Bypass a capability check in newer IEs to avoid a "this page uses Java" dialog. --- src/prototype/dom/dom.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index eee4fddd7..959e8bc48 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -3170,6 +3170,9 @@ // Certain oddball element types can't be extended in IE8. function checkElementPrototypeDeficiency(tagName) { if (typeof window.Element === 'undefined') return false; + // Skip newer IEs because creating an OBJECT tag pops up an annoying + // "this page uses Java" warning. + if (!HAS_EXTENDED_CREATE_ELEMENT_SYNTAX) return false; var proto = window.Element.prototype; if (proto) { var id = '_' + (Math.random() + '').slice(2), From b2b2e2751b725941a984a38348b66f8b31ccc017 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 18 Apr 2014 19:59:43 -0500 Subject: [PATCH 414/502] Remove old version of Sizzle in source tree. The distribution is built with the version in the Sizzle submodule. It's unclear to me that these files were _ever_ used for building a distributable prototype.js. If there are good reasons to keep them around, I'm happy to hear them. Otherwise they just create confusion. --- src/selector_engine.js | 24 - src/sizzle.js | 1413 ---------------------------------------- 2 files changed, 1437 deletions(-) delete mode 100644 src/selector_engine.js delete mode 100644 src/sizzle.js diff --git a/src/selector_engine.js b/src/selector_engine.js deleted file mode 100644 index b8deea890..000000000 --- a/src/selector_engine.js +++ /dev/null @@ -1,24 +0,0 @@ -//= compat -//= require "sizzle" - -Prototype._original_property = window.Sizzle; - -;(function(engine) { - var extendElements = Prototype.Selector.extendElements; - - function select(selector, scope) { - return extendElements(engine(selector, scope || document)); - } - - function match(element, selector) { - return engine.matches(selector, [element]).length == 1; - } - - Prototype.Selector.engine = engine; - Prototype.Selector.select = select; - Prototype.Selector.match = match; -})(Sizzle); - -// Restore globals. -window.Sizzle = Prototype._original_property; -delete Prototype._original_property; diff --git a/src/sizzle.js b/src/sizzle.js deleted file mode 100644 index 07654d594..000000000 --- a/src/sizzle.js +++ /dev/null @@ -1,1413 +0,0 @@ -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var match, - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var found, item, - filter = Expr.filter[ type ], - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - var attr = elem.getAttribute( "type" ), type = elem.type; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); - }, - - radio: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && "button" === elem.type || name === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - }, - - focus: function( elem ) { - return elem === elem.ownerDocument.activeElement; - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - var first = match[2], - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); - } - } - - return ret; -}; - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "

    "; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - - if ( matches ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9 fails this) - var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - var ret = matches.call( node, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || !disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9, so check for that - node.document && node.document.nodeType !== 11 ) { - return ret; - } - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
    "; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE - -window.Sizzle = Sizzle; - -})(); From d9411e572431f2c69089fc78980f5d9b2786d4fb Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Fri, 18 Apr 2014 21:01:15 -0500 Subject: [PATCH 415/502] Bump version number. --- src/constants.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.yml b/src/constants.yml index c85abf6c6..f230580c3 100644 --- a/src/constants.yml +++ b/src/constants.yml @@ -1 +1 @@ -PROTOTYPE_VERSION: 1.7.1 +PROTOTYPE_VERSION: 1.7.2 From 548580cc8d162d95c60c3b868ffe0216ba209ab5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 21 Apr 2014 22:52:01 -0500 Subject: [PATCH 416/502] Fix issue with `Element.visible` reading only inline style. [closes #131] --- src/prototype/dom/dom.js | 2 +- test/unit/dom_test.js | 4 ++++ test/unit/fixtures/dom.html | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 959e8bc48..8fbd5b378 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -290,7 +290,7 @@ * // -> true **/ function visible(element) { - return $(element).style.display !== 'none'; + return $(element).getStyle('display') !== 'none'; } /** diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 54868213b..d62a4fde8 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -290,6 +290,10 @@ new Test.Unit.Runner({ testElementVisible: function(){ this.assertNotEqual('none', $('test-visible').style.display); this.assertEqual('none', $('test-hidden').style.display); + + this.assert($('test-visible').visible()); + this.assert(!$('test-invisible').visible()); + this.assert(!$('test-hidden').visible()); }, testElementToggle: function(){ diff --git a/test/unit/fixtures/dom.html b/test/unit/fixtures/dom.html index 99395eeca..64ae88649 100644 --- a/test/unit/fixtures/dom.html +++ b/test/unit/fixtures/dom.html @@ -2,7 +2,12 @@

    Scroll test

    + +
    visible
    +
    invisible
    visible
    From 2c2356251cec90c800e63a5d09d33b47d5430430 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 21 Apr 2014 22:53:39 -0500 Subject: [PATCH 417/502] Update documentation for `Element.visible`. --- src/prototype/dom/dom.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 8fbd5b378..6bfffcbd7 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -267,27 +267,6 @@ * * $('hidden').visible(); * // -> false - * - * ##### Notes - * - * Styles applied via a CSS stylesheet are _not_ taken into consideration. - * Note that this is not a Prototype limitation, it is a CSS limitation. - * - * language: html - * - * - * [...] - * - *
    - * - * And the associated JavaScript: - * - * $('hidden-by-css').visible(); - * // -> true **/ function visible(element) { return $(element).getStyle('display') !== 'none'; From 72ff0df459d32b7b61318f14f81ee93fc23a6a05 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 27 Apr 2014 15:56:26 -0500 Subject: [PATCH 418/502] Fix regression with handling of `$break` inside `Array#inject`. [close #162] --- src/prototype/lang/array.js | 20 ++++---------------- test/unit/enumerable_test.js | 7 +++++++ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/prototype/lang/array.js b/src/prototype/lang/array.js index 2a5f0655f..2b6133494 100644 --- a/src/prototype/lang/array.js +++ b/src/prototype/lang/array.js @@ -720,22 +720,11 @@ Array.from = $A; var every = wrapNative(Array.prototype.every); } - // Prototype's `Array#inject` behaves similarly to ES5's `Array#reduce`. - var _reduce = arrayProto.reduce; - function inject(memo, iterator) { - iterator = iterator || Prototype.K; - var context = arguments[2]; - // The iterator must be bound, as `Array#reduce` always binds to - // `undefined`. - return _reduce.call(this, iterator.bind(context), memo); - } + // We used to define an `inject` method here that relied on ES5's + // `Array#reduce` (if present), but using `reduce` prevents us from + // catching a thrown `$break`. So arrays now use the standard + // `Enumerable.inject` like they did previously. - // Piggyback on `Array#reduce` if it exists; otherwise fall back to the - // standard `Enumerable.inject`. - if (!arrayProto.reduce) { - var inject = Enumerable.inject; - } - Object.extend(arrayProto, Enumerable); if (!arrayProto._reverse) @@ -753,7 +742,6 @@ Array.from = $A; any: some, every: every, all: every, - inject: inject, clear: clear, first: first, diff --git a/test/unit/enumerable_test.js b/test/unit/enumerable_test.js index 27bd024c5..74df7c404 100644 --- a/test/unit/enumerable_test.js +++ b/test/unit/enumerable_test.js @@ -271,6 +271,13 @@ new Test.Unit.Runner({ Fixtures.Primes.inject(0, function(sum, value) { return sum + value; })); + + var sum = Fixtures.Primes.inject(0, function(sum, value) { + if (value === 5) throw $break; + return sum + value; + }); + + this.assertEqual(6, sum, 'inject should catch a thrown $break'); }, "test #inject passes memo, value, index and collection to the iterator": function() { From 94e2863f5e6da2366053ab5c5a86958cf47ae458 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 27 Apr 2014 16:05:00 -0500 Subject: [PATCH 419/502] Fix error in `Element.positionedOffset` that made incorrect adjustments for margins. [close #141] --- src/prototype/dom/layout.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/prototype/dom/layout.js b/src/prototype/dom/layout.js index e389f8aa8..860caa4af 100644 --- a/src/prototype/dom/layout.js +++ b/src/prototype/dom/layout.js @@ -1114,8 +1114,8 @@ } } while (element); - valueL -= layout.get('margin-top'); - valueT -= layout.get('margin-left'); + valueL -= layout.get('margin-left'); + valueT -= layout.get('margin-top'); return new Element.Offset(valueL, valueT); } From 9ca2e6a0445a77858d8a84588c03901c2e0b45b4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 27 Apr 2014 16:47:22 -0500 Subject: [PATCH 420/502] Fix issue where `getOpacity` would return a string, not a number, in newer versions of IE. Also fix documentation that incorrectly claimed `getOpacity` was designed to return a string. [close #116] --- src/prototype/dom/dom.js | 17 ++++++++++++----- test/unit/dom_test.js | 4 +++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 6bfffcbd7..f6dd2237d 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -2792,7 +2792,7 @@ /** - * Element.getStyle(@element, style) -> String | null + * Element.getStyle(@element, style) -> String | Number | null * - style (String): The property name to be retrieved. * * Returns the given CSS property value of `element`. The property can be @@ -2805,6 +2805,10 @@ * (fully transparent) and `1` (fully opaque), position properties * (`left`, `top`, `right` and `bottom`) and when getting the dimensions * (`width` or `height`) of hidden elements. + * + * If a value is present, it will be returned as a string — except + * for `opacity`, which returns a number between `0` and `1` just as + * [[Element.getOpacity]] does. * * ##### Examples * @@ -2915,9 +2919,12 @@ value = element.currentStyle[style]; } - if (style === 'opacity' && !STANDARD_CSS_OPACITY_SUPPORTED) - return getOpacity_IE(element); - + if (style === 'opacity') { + if (!STANDARD_CSS_OPACITY_SUPPORTED) + return getOpacity_IE(element); + else return value ? parseFloat(value) : 1.0; + } + if (value === 'auto') { // If we need a dimension, return null for hidden elements, but return // pixel values for visible elements. @@ -3006,7 +3013,7 @@ /** - * Element.getOpacity(@element) -> String | null + * Element.getOpacity(@element) -> Number | null * * Returns the opacity of the element. **/ diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index d62a4fde8..397b0d3b6 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -1038,7 +1038,9 @@ new Test.Unit.Runner({ }, testElementGetOpacity: function() { - this.assertEqual(0.45, $('op1').setOpacity(0.45).getOpacity()); + var opacity = $('op1').setOpacity(0.45).getOpacity(); + this.assertEqual(0.45, opacity); + this.assertEqual('number', typeof opacity, 'opacity should be a string, not a number'); }, testElementReadAttribute: function() { From ded0dde6ebb869e96bf5fce670938129239a3d2f Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 27 Apr 2014 17:15:43 -0500 Subject: [PATCH 421/502] Fix regression in behavior of `someElements.each(Element.toggle)`. [close #136] --- src/prototype/dom/dom.js | 11 ++++++++++- test/unit/dom_test.js | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index fd4abef9d..a09b8588c 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -274,6 +274,8 @@ /** * Element.toggle(@element[, bool]) -> Element + * - bool (Boolean): Whether the element should be shown or hidden. If not + a boolean, this argument will be ignored. * * Toggles the CSS `display` of `element`. Returns `element`. * @@ -284,6 +286,13 @@ * current state, but will use the `bool` argument instead if it's * provided (`true` to show the element, `false` to hide it). * + * If the `bool` argument is not a boolean, **it will be ignored**. This + * preserves the ability to toggle elements through comparisons (e.g., + * `errorElement.toggle(errors > 0)`) while also letting a user do + * `someElements.each(Element.toggle)` without falling victim to + * JavaScript's famous [problems with variadic arguments](http://www.wirfs-brock.com/allen/posts/166). + * + * * ##### Examples * *
    Welcome
    @@ -335,7 +344,7 @@ **/ function toggle(element, bool) { element = $(element); - if (Object.isUndefined(bool)) + if (typeof bool !== 'boolean') bool = !Element.visible(element); Element[bool ? 'show' : 'hide'](element); diff --git a/test/unit/dom_test.js b/test/unit/dom_test.js index 397b0d3b6..1ab641918 100644 --- a/test/unit/dom_test.js +++ b/test/unit/dom_test.js @@ -305,6 +305,10 @@ new Test.Unit.Runner({ this.assert($('test-toggle-hidden').visible(), 'test-toggle-hidden 1'); $('test-toggle-hidden').toggle(); this.assert(!$('test-toggle-hidden').visible(), 'test-toggle-hidden 2'); + + $('test-toggle-hidden', 'test-toggle-visible').each(Element.toggle); + this.assert(!$('test-toggle-visible').visible(), 'test-toggle-visible-3'); + this.assert($('test-toggle-hidden').visible(), 'test-toggle-hidden-3'); }, testElementShow: function(){ From 7240a87771144634f105e3ee83c1708aac96858e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 27 Apr 2014 17:39:50 -0500 Subject: [PATCH 422/502] Add documentation to explain how `HTMLSelectElement#remove` clashes with `Element#remove`. [close #122] --- src/prototype/dom/dom.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index a09b8588c..71331be24 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -472,6 +472,15 @@ *
  • McIntosh
  • *
  • Ida Red
  • *

+ * + * ##### Warning + * + * Using [[Element.remove]] as an instance method (e.g., + * `$('foo').remove('')`) won't work when the element in question is a + * `select` element, since`select` elements have [an existing `remove` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement) + * that behaves differently from this method. As a workaround, use the + * generic version instead (`Element.remove('foo')`). + * **/ function remove(element) { element = $(element); From 41f2d65b43028218a244f776a5735454cbce6bef Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 30 Apr 2014 15:26:53 -0500 Subject: [PATCH 423/502] Fix PDoc error. --- src/prototype/dom/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prototype/dom/dom.js b/src/prototype/dom/dom.js index 71331be24..b9500f0dc 100644 --- a/src/prototype/dom/dom.js +++ b/src/prototype/dom/dom.js @@ -275,7 +275,7 @@ /** * Element.toggle(@element[, bool]) -> Element * - bool (Boolean): Whether the element should be shown or hidden. If not - a boolean, this argument will be ignored. + * a boolean, this argument will be ignored. * * Toggles the CSS `display` of `element`. Returns `element`. * From a6aca89e82a7369d3c9fcffebd35b35f7f8628a5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 30 Apr 2014 15:27:00 -0500 Subject: [PATCH 424/502] Update to latest PDoc. --- vendor/pdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pdoc b/vendor/pdoc index b92409f60..b8da8a0e3 160000 --- a/vendor/pdoc +++ b/vendor/pdoc @@ -1 +1 @@ -Subproject commit b92409f605a65c94fcfd0df68fdd830ff9ca2195 +Subproject commit b8da8a0e3e33192bf74a0eecdcb88108bf9adcfd From eb5dc037f73ea2573ddfeeb1a78f67a0ed75a7a6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 30 Apr 2014 15:36:40 -0500 Subject: [PATCH 425/502] Rakefile fixes. * Mixin `Rake::DSL` into our helper module, thus getting rid of the annoying warning from rake. * Update the voodoo we use to recover from a LoadError, since the error message appears to have changed from 1.8 to 1.9. --- Rakefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e6f661481..0df098ec5 100755 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,8 @@ require 'rake/packagetask' require 'yaml' module PrototypeHelper + extend Rake::DSL + ROOT_DIR = File.expand_path(File.dirname(__FILE__)) SRC_DIR = File.join(ROOT_DIR, 'src') DIST_DIR = File.join(ROOT_DIR, 'dist') @@ -197,7 +199,7 @@ EOF # Wait until we notice that a submodule is missing before we bother the # user about installing git. (Maybe they brought all the files over # from a different machine.) - missing_file = e.message.sub('no such file to load -- ', '') + missing_file = e.message.sub('no such file to load -- ', '').sub('cannot load such file -- ', '') if missing_file == path # Missing a git submodule. retry if get_submodule(name, path) From 55893a221b60b4a279ae295829777d37ca424058 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 10 May 2014 13:40:47 -0500 Subject: [PATCH 426/502] Fix issue where the deprecated Element#childOf was not initially present on elements. We added it to the Element.Methods object in deprecated.js _after_ the initial call to Element.addMethods, meaning that it didn't get copied over unless the user made a subsequent call to Element.addMethods for whatever reason. Unit tests didn't catch this because they call Element.addMethods before testing starts. --- src/prototype/deprecated.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/prototype/deprecated.js b/src/prototype/deprecated.js index 26351b7a5..6859bd96f 100644 --- a/src/prototype/deprecated.js +++ b/src/prototype/deprecated.js @@ -4,7 +4,9 @@ Hash.toQueryString = Object.toQueryString; var Toggle = { display: Element.toggle }; -Element.Methods.childOf = Element.Methods.descendantOf; +Element.addMethods({ + childOf: Element.Methods.descendantOf +}); var Insertion = { Before: function(element, content) { @@ -202,7 +204,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); initialize: function(expression) { this.expression = expression.strip(); }, - + /** deprecated * Selector#findElements(root) -> [Element...] * - root (Element | document): A "scope" to search within. All results will @@ -214,7 +216,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); findElements: function(rootElement) { return Prototype.Selector.select(this.expression, rootElement); }, - + /** deprecated * Selector#match(element) -> Boolean * @@ -223,11 +225,11 @@ Object.extend(Element.ClassNames.prototype, Enumerable); match: function(element) { return Prototype.Selector.match(element, this.expression); }, - + toString: function() { return this.expression; }, - + inspect: function() { return "#"; } @@ -244,7 +246,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); matchElements: function(elements, expression) { var match = Prototype.Selector.match, results = []; - + for (var i = 0, length = elements.length; i < length; i++) { var element = elements[i]; if (match(element, expression)) { From 8d6a7cb5f8e019bb3971a9f0bf80b8eb0acc1b77 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 17:11:16 -0500 Subject: [PATCH 427/502] Remove the event registry from an element when its last observer is removed. --- src/prototype/dom/event.js | 397 +++++++++++++++++++------------------ test/functional/event.html | 108 +++++----- 2 files changed, 257 insertions(+), 248 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index b9811a0ea..40e35e4e2 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -32,33 +32,33 @@ * The functions you're most likely to use a lot are [[Event.observe]], * [[Event.element]] and [[Event.stop]]. If your web app uses custom events, * you'll also get a lot of mileage out of [[Event.fire]]. - * + * * ##### Instance methods on event objects - * As of Prototype 1.6, all methods on the `Event` object are now also + * As of Prototype 1.6, all methods on the `Event` object are now also * available as instance methods on the event object itself: - * + * * **Before** - * + * * $('foo').observe('click', respondToClick); - * + * * function respondToClick(event) { * var element = Event.element(event); * element.addClassName('active'); * } - * + * * **After** - * + * * $('foo').observe('click', respondToClick); - * + * * function respondToClick(event) { * var element = event.element(); * element.addClassName('active'); * } - * + * * These methods are added to the event object through [[Event.extend]], - * in the same way that `Element` methods are added to DOM nodes through - * [[Element.extend]]. Events are extended automatically when handlers are - * registered with Prototype's [[Event.observe]] method; if you're using a + * in the same way that `Element` methods are added to DOM nodes through + * [[Element.extend]]. Events are extended automatically when handlers are + * registered with Prototype's [[Event.observe]] method; if you're using a * different method of event registration, for whatever reason,you'll need to * extend these events manually with [[Event.extend]]. **/ @@ -66,7 +66,7 @@ var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; - + var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, @@ -83,7 +83,7 @@ KEY_PAGEDOWN: 34, KEY_INSERT: 45 }; - + // We need to support three different event "modes": // 1. browsers with only DOM L2 Events (WebKit, FireFox); // 2. browsers with only IE's legacy events system (IE 6-8); @@ -108,7 +108,7 @@ isIELegacyEvent = function(event) { return true; }; } } - + // The two systems have different ways of indicating which button was used // for a mouse event. var _isButton; @@ -135,7 +135,7 @@ if (window.attachEvent) { if (!window.addEventListener) { // Legacy IE events only. - _isButton = _isButtonForLegacyEvents; + _isButton = _isButtonForLegacyEvents; } else { // Both systems are supported; decide at runtime. _isButton = function(event, code) { @@ -148,7 +148,7 @@ } else { _isButton = _isButtonForDOMEvents; } - + /** * Event.isLeftClick(@event) -> Boolean * - event (Event): An Event object @@ -183,37 +183,37 @@ * report clicks of the _left_ button as "left-clicks." **/ function isRightClick(event) { return _isButton(event, 2) } - + /** deprecated * Event.element(@event) -> Element * - event (Event): An Event object * * Returns the DOM element on which the event occurred. This method * is deprecated, use [[Event.findElement]] instead. - * + * * ##### Example - * + * * Here's a simple bit of code which hides any paragraph when directly clicked. - * + * * document.observe('click', function(event) { * var element = Event.element(event); * if ('P' == element.tagName) * element.hide(); * }); - * + * * ##### See also - * - * There is a subtle distinction between this function and + * + * There is a subtle distinction between this function and * [[Event.findElement]]. - * + * * ##### Note for Prototype 1.5.0 - * + * * Note that prior to version 1.5.1, if the browser does not support * *native DOM extensions* (see the [[Element]] section for further details), * the element returned by [[Event.element]] might very well * *not be extended*. If you intend to use methods from [[Element.Methods]] * on it, you need to wrap the call in the [[$]] function like so: - * + * * document.observe('click', function(event) { * var element = $(Event.element(event)); * // ... @@ -225,7 +225,7 @@ // internally as `_element` without having to extend the node. return Element.extend(_element(event)); } - + function _element(event) { event = Event.extend(event); @@ -256,14 +256,14 @@ * starting with the element on which the event occurred, then moving up * its ancestor chain. If `expression` is not given, the element which fired * the event is returned. - * + * * *If no matching element is found, `undefined` is returned.* - * + * * ##### Example - * + * * Here's a simple example that lets you click everywhere on the page and * hides the closest-fitting paragraph around your click (if any). - * + * * document.observe('click', function(event) { * var element = event.findElement('p'); * if (element) @@ -279,7 +279,7 @@ element = element.parentNode; } } - + /** * Event.pointer(@event) -> Object * @@ -341,33 +341,33 @@ * * Stopping an event also sets a `stopped` property on that event for * future inspection. - * + * * There are two aspects to how your browser handles an event once it fires up: - * - * 1. The browser usually triggers event handlers on the actual element the - * event occurred on, then on its parent element, and so on and so forth, - * until the document's root element is reached. This is called - * *event bubbling*, and is the most common form of event propagation. You - * may very well want to stop this propagation when you just handled an event, + * + * 1. The browser usually triggers event handlers on the actual element the + * event occurred on, then on its parent element, and so on and so forth, + * until the document's root element is reached. This is called + * *event bubbling*, and is the most common form of event propagation. You + * may very well want to stop this propagation when you just handled an event, * and don't want it to keep bubbling up (or see no need for it). - * - * 2. Once your code had a chance to process the event, the browser handles - * it as well, if that event has a *default behavior*. For instance, clicking - * on links navigates to them; submitting forms sends them over to the server - * side; hitting the Return key in a single-line form field submits it; etc. - * You may very well want to prevent this default behavior if you do your own + * + * 2. Once your code had a chance to process the event, the browser handles + * it as well, if that event has a *default behavior*. For instance, clicking + * on links navigates to them; submitting forms sends them over to the server + * side; hitting the Return key in a single-line form field submits it; etc. + * You may very well want to prevent this default behavior if you do your own * handling. - * - * Because stopping one of those aspects means, in 99.9% of the cases, - * preventing the other one as well, Prototype bundles both in this `stop` - * function. Calling it on an event object, stops propagation *and* prevents + * + * Because stopping one of those aspects means, in 99.9% of the cases, + * preventing the other one as well, Prototype bundles both in this `stop` + * function. Calling it on an event object, stops propagation *and* prevents * the default behavior. - * + * * ##### Example - * - * Here's a simple script that prevents a form from being sent to the server + * + * Here's a simple script that prevents a form from being sent to the server * side if certain field is empty. - * + * * Event.observe('signinForm', 'submit', function(event) { * var login = $F('login').strip(); * if ('' == login) { @@ -375,7 +375,7 @@ * // Display the issue one way or another * } * }); - **/ + **/ function stop(event) { Event.extend(event); event.preventDefault(); @@ -438,27 +438,27 @@ /** * Event.extend(@event) -> Event * - event (Event): An Event object - * + * * Extends `event` with all of the methods contained in `Event.Methods`. - * - * Note that all events inside handlers that were registered using + * + * Note that all events inside handlers that were registered using * [[Event.observe]] or [[Element.observe]] will be extended automatically. - * - * You need only call `Event.extend` manually if you register a handler a + * + * You need only call `Event.extend` manually if you register a handler a * different way (e.g., the `onclick` attribute). We really can't encourage * that sort of thing, though. **/ // IE's method for extending events. Event.extend = function(event, element) { if (!event) return false; - + // If it's not a legacy event, it doesn't need extending. if (!isIELegacyEvent(event)) return event; // Mark this event so we know not to extend a second time. if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; - + var pointer = Event.pointer(event); // The optional `element` argument gives us a fallback value for the @@ -469,24 +469,24 @@ pageX: pointer.x, pageY: pointer.y }); - + Object.extend(event, methods); Object.extend(event, additionalMethods); - + return event; }; } else { // Only DOM events, so no manual extending necessary. Event.extend = Prototype.K; } - + if (window.addEventListener) { // In all browsers that support DOM L2 Events, we can augment // `Event.prototype` directly. Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); } - + // // EVENT REGISTRY // @@ -494,14 +494,14 @@ mouseenter: 'mouseover', mouseleave: 'mouseout' }; - + function getDOMEventName(eventName) { return EVENT_TRANSLATIONS[eventName] || eventName; } - + if (MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) getDOMEventName = Prototype.K; - + function getUniqueElementID(element) { if (element === window) return 0; @@ -511,7 +511,7 @@ element._prototypeUID = Element.Storage.UID++; return element._prototypeUID; } - + // In Internet Explorer, DOM nodes have a `uniqueID` property. Saves us // from inventing our own. function getUniqueElementID_IE(element) { @@ -520,7 +520,7 @@ if (element == document) return 1; return element.uniqueID; } - + if ('uniqueID' in DIV) getUniqueElementID = getUniqueElementID_IE; @@ -539,17 +539,17 @@ if (!CACHE[uid]) CACHE[uid] = { element: element }; return CACHE[uid]; } - + function destroyRegistryForElement(element, uid) { if (Object.isUndefined(uid)) uid = getUniqueElementID(element); delete GLOBAL.Event.cache[uid]; } - + // The `register` and `unregister` functions handle creating the responder // and managing an event registry. They _don't_ attach and detach the // listeners themselves. - + // Add an event to the element's event registry. function register(element, eventName, handler) { var registry = getRegistryForElement(element); @@ -560,7 +560,7 @@ var i = entries.length; while (i--) if (entries[i].handler === handler) return null; - + var uid = getUniqueElementID(element); var responder = GLOBAL.Event._createResponder(uid, eventName, handler); var entry = { @@ -568,16 +568,16 @@ handler: handler }; - entries.push(entry); + entries.push(entry); return entry; } - + // Remove an event from the element's event registry. function unregister(element, eventName, handler) { var registry = getRegistryForElement(element); var entries = registry[eventName]; if (!entries) return; - + var i = entries.length, entry; while (i--) { if (entries[i].handler === handler) { @@ -585,7 +585,7 @@ break; } } - + // This handler wasn't in the collection, so it doesn't need to be // unregistered. if (!entry) return; @@ -593,11 +593,20 @@ // Remove the entry from the collection; var index = entries.indexOf(entry); entries.splice(index, 1); - + + if (entries.length === 0) { + // We can destroy the registry's entry for this event name... + delete registry[eventName]; + // ...and we we should destroy the whole registry if there are no other + // events. + if (Object.keys(registry).length === 1 && ('element' in registry)) + destroyRegistryForElement(element); + } + return entry; - } - - + } + + // // EVENT OBSERVING // @@ -751,22 +760,22 @@ * 1.6 also introduced setting the `this` context to the element being * observed, automatically extending the [[Event]] object, and the * [[Event#findElement]] method. - **/ + **/ function observe(element, eventName, handler) { element = $(element); var entry = register(element, eventName, handler); - + if (entry === null) return element; - var responder = entry.responder; + var responder = entry.responder; if (isCustomEvent(eventName)) observeCustomEvent(element, eventName, responder); else observeStandardEvent(element, eventName, responder); - + return element; } - + function observeStandardEvent(element, eventName, responder) { var actualEventName = getDOMEventName(eventName); if (element.addEventListener) { @@ -775,7 +784,7 @@ element.attachEvent('on' + actualEventName, responder); } } - + function observeCustomEvent(element, eventName, responder) { if (element.addEventListener) { element.addEventListener('dataavailable', responder, false); @@ -786,7 +795,7 @@ element.attachEvent('onlosecapture', responder); } } - + /** * Event.stopObserving(element[, eventName[, handler]]) -> Element * - element (Element | String): The element to stop observing, or its ID. @@ -848,38 +857,38 @@ * ...and then to remove: * * $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right - **/ + **/ function stopObserving(element, eventName, handler) { element = $(element); var handlerGiven = !Object.isUndefined(handler), eventNameGiven = !Object.isUndefined(eventName); - + if (!eventNameGiven && !handlerGiven) { stopObservingElement(element); return element; } - + if (!handlerGiven) { stopObservingEventName(element, eventName); return element; } - + var entry = unregister(element, eventName, handler); - - if (!entry) return element; + + if (!entry) return element; removeEvent(element, eventName, entry.responder); return element; } - + function stopObservingStandardEvent(element, eventName, responder) { var actualEventName = getDOMEventName(eventName); if (element.removeEventListener) { - element.removeEventListener(actualEventName, responder, false); + element.removeEventListener(actualEventName, responder, false); } else { element.detachEvent('on' + actualEventName, responder); } } - + function stopObservingCustomEvent(element, eventName, responder) { if (element.removeEventListener) { element.removeEventListener('dataavailable', responder, false); @@ -888,7 +897,7 @@ element.detachEvent('onlosecapture', responder); } } - + // The `stopObservingElement` and `stopObservingEventName` functions are // for bulk removal of event listeners. We use them rather than recurse @@ -917,29 +926,29 @@ removeEvent(element, eventName, entries[i].responder); } } - + // Stop observing all listeners of a certain event name on an element. function stopObservingEventName(element, eventName) { var registry = getRegistryForElement(element); var entries = registry[eventName]; if (!entries) return; delete registry[eventName]; - + var i = entries.length; while (i--) removeEvent(element, eventName, entries[i].responder); } - + function removeEvent(element, eventName, handler) { if (isCustomEvent(eventName)) stopObservingCustomEvent(element, eventName, handler); else stopObservingStandardEvent(element, eventName, handler); } - - - + + + // FIRING CUSTOM EVENTS function getFireTarget(element) { if (element !== document) return element; @@ -947,7 +956,7 @@ return document.documentElement; return element; } - + /** * Event.fire(element, eventName[, memo[, bubble = true]]) -> Event * - memo (?): Metadata for the event. Will be accessible to event @@ -960,50 +969,50 @@ **/ function fire(element, eventName, memo, bubble) { element = getFireTarget($(element)); - if (Object.isUndefined(bubble)) bubble = true; + if (Object.isUndefined(bubble)) bubble = true; memo = memo || {}; - + var event = fireEvent(element, eventName, memo, bubble); return Event.extend(event); } - + function fireEvent_DOM(element, eventName, memo, bubble) { var event = document.createEvent('HTMLEvents'); event.initEvent('dataavailable', bubble, true); - + event.eventName = eventName; event.memo = memo; - + element.dispatchEvent(event); return event; } - + function fireEvent_IE(element, eventName, memo, bubble) { var event = document.createEventObject(); event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; - + event.eventName = eventName; event.memo = memo; - - element.fireEvent(event.eventType, event); + + element.fireEvent(event.eventType, event); return event; } - + var fireEvent = document.createEvent ? fireEvent_DOM : fireEvent_IE; - - + + // EVENT DELEGATION - + /** * class Event.Handler - * + * * Creates an observer on an element that listens for a particular event on * that element's descendants, optionally filtering by a CSS selector. - * + * * This class simplifies the common "event delegation" pattern, in which one * avoids adding an observer to a number of individual elements and instead * listens on a _common ancestor_ element. - * + * * For more information on usage, see [[Event.on]]. **/ Event.Handler = Class.create({ @@ -1021,7 +1030,7 @@ * event. (If `selector` was given, this element will be the one that * satisfies the criteria described just above; if not, it will be the * one specified in the `element` argument). - * + * * Instantiates an `Event.Handler`. **Will not** begin observing until * [[Event.Handler#start]] is called. **/ @@ -1032,34 +1041,34 @@ this.callback = callback; this.handler = this.handleEvent.bind(this); }, - + /** * Event.Handler#start -> Event.Handler - * + * * Starts listening for events. Returns itself. **/ start: function() { Event.observe(this.element, this.eventName, this.handler); return this; }, - + /** * Event.Handler#stop -> Event.Handler - * + * * Stops listening for events. Returns itself. **/ stop: function() { Event.stopObserving(this.element, this.eventName, this.handler); return this; }, - + handleEvent: function(event) { var element = Event.findElement(event, this.selector); if (element) this.callback.call(this.element, event, element); } }); - + /** * Event.on(element, eventName[, selector], callback) -> Event.Handler * - element (Element | String): The DOM element to observe, or its ID. @@ -1075,53 +1084,53 @@ * satisfies the criteria described just above; if not, it will be the * one specified in the `element` argument). This function is **always** * bound to `element`. - * + * * Listens for events on an element's descendants, optionally filtering * to match a given CSS selector. - * + * * Creates an instance of [[Event.Handler]], calls [[Event.Handler#start]], * then returns that instance. Keep a reference to this returned instance if * you later want to unregister the observer. - * + * * ##### Usage - * + * * `Event.on` can be used to set up event handlers with or without event * delegation. In its simplest form, it works just like [[Event.observe]]: - * + * * $("messages").on("click", function(event) { * // ... * }); - * + * * An optional second argument lets you specify a CSS selector for event * delegation. This encapsulates the pattern of using [[Event#findElement]] * to retrieve the first ancestor element matching a specific selector. - * + * * $("messages").on("click", "a.comment", function(event, element) { * // ... * }); - * + * * Note the second argument in the handler above: it references the * element matched by the selector (in this case, an `a` tag with a class * of `comment`). This argument is important to use because within the * callback, the `this` keyword **will always refer to the original * element** (in this case, the element with the id of `messages`). - * + * * `Event.on` differs from `Event.observe` in one other important way: * its return value is an instance of [[Event.Handler]]. This instance * has a `stop` method that will remove the event handler when invoked * (and a `start` method that will attach the event handler again after * it's been removed). - * + * * // Register the handler: * var handler = $("messages").on("click", "a.comment", * this.click.bind(this)); - * + * * // Unregister the handler: * handler.stop(); - * + * * // Re-register the handler: * handler.start(); - * + * * This means that, unlike `Event.stopObserving`, there's no need to * retain a reference to the handler function. **/ @@ -1130,10 +1139,10 @@ if (Object.isFunction(selector) && Object.isUndefined(callback)) { callback = selector, selector = null; } - + return new Event.Handler(element, eventName, selector, callback).start(); } - + Object.extend(Event, Event.Methods); Object.extend(Event, { @@ -1150,41 +1159,41 @@ * See [[Event.fire]]. * * Fires a custom event with the current element as its target. - * + * * [[Element.fire]] creates a custom event with the given name, then triggers * it on the given element. The custom event has all the same properties * and methods of native events. Like a native event, it will bubble up * through the DOM unless its propagation is explicitly stopped. - * + * * The optional second argument will be assigned to the `memo` property of * the event object so that it can be read by event handlers. - * + * * Custom events are dispatched synchronously: [[Element.fire]] waits until * the event finishes its life cycle, then returns the event itself. - * + * * ##### Note - * + * * [[Element.fire]] does not support firing native events. All custom event * names _must_ be namespaced (using a colon). This is to avoid custom * event names conflicting with non-standard native DOM events such as * `mousewheel` and `DOMMouseScroll`. - * + * * ##### Examples - * + * * document.observe("widget:frobbed", function(event) { * console.log("Element with ID (" + event.target.id + * ") frobbed widget #" + event.memo.widgetNumber + "."); * }); - * + * * var someNode = $('foo'); * someNode.fire("widget:frobbed", { widgetNumber: 19 }); - * + * * //-> "Element with ID (foo) frobbed widget #19." - * + * * ##### Tip - * + * * Events that have been stopped with [[Event.stop]] will have a boolean - * `stopped` property set to true. Since [[Element.fire]] returns the custom + * `stopped` property set to true. Since [[Element.fire]] returns the custom * event, you can inspect this property to determine whether the event was * stopped. **/ @@ -1203,7 +1212,7 @@ * See [[Event.stopObserving]]. **/ stopObserving: stopObserving, - + /** * Element.on(@element, eventName[, selector], callback) -> Element * @@ -1238,13 +1247,13 @@ * * Listens for the given event over the entire document. Can also be used * for listening to `"dom:loaded"` event. - * + * * [[document.observe]] is the document-wide version of [[Element#observe]]. * Using [[document.observe]] is equivalent to * `Event.observe(document, eventName, handler)`. - * + * * ##### The `"dom:loaded"` event - * + * * One really useful event generated by Prototype that you might want to * observe on the document is `"dom:loaded"`. On supporting browsers it * fires on `DOMContentLoaded` and on unsupporting browsers it simulates it @@ -1254,9 +1263,9 @@ * fully loaded. The `load` event on `window` only fires after all page * images are loaded, making it unsuitable for some initialization purposes * like hiding page elements (so they can be shown later). - * + * * ##### Example - * + * * document.observe("dom:loaded", function() { * // initially hide all containers for tab content * $$('div.tabcontent').invoke('hide'); @@ -1268,15 +1277,15 @@ * document.stopObserving([eventName[, handler]]) -> Element * * Unregisters an event handler from the document. - * + * * [[document.stopObserving]] is the document-wide version of * [[Element.stopObserving]]. **/ stopObserving: stopObserving.methodize(), - + /** * document.on(@element, eventName[, selector], callback) -> Event.Handler - * + * * See [[Event.on]]. **/ on: on.methodize(), @@ -1292,74 +1301,74 @@ // Export to the global scope. if (GLOBAL.Event) Object.extend(window.Event, Event); else GLOBAL.Event = Event; - + GLOBAL.Event.cache = {}; - + function destroyCache_IE() { GLOBAL.Event.cache = null; } - + if (window.attachEvent) window.attachEvent('onunload', destroyCache_IE); - + DIV = null; docEl = null; })(this); -(function(GLOBAL) { +(function(GLOBAL) { /* Code for creating leak-free event responders is based on work by John-David Dalton. */ - + var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; - + function isSimulatedMouseEnterLeaveEvent(eventName) { return !MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === 'mouseenter' || eventName === 'mouseleave'); } - + // The functions for creating responders accept the element's UID rather // than the element itself. This way, there are _no_ DOM objects inside the // closure we create, meaning there's no need to unregister event listeners // on unload. - function createResponder(uid, eventName, handler) { + function createResponder(uid, eventName, handler) { if (Event._isCustomEvent(eventName)) - return createResponderForCustomEvent(uid, eventName, handler); + return createResponderForCustomEvent(uid, eventName, handler); if (isSimulatedMouseEnterLeaveEvent(eventName)) return createMouseEnterLeaveResponder(uid, eventName, handler); - + return function(event) { if (!Event.cache) return; - + var element = Event.cache[uid].element; Event.extend(event, element); handler.call(element, event); }; } - + function createResponderForCustomEvent(uid, eventName, handler) { return function(event) { var element = Event.cache[uid].element; if (Object.isUndefined(event.eventName)) return false; - + if (event.eventName !== eventName) return false; - + Event.extend(event, element); handler.call(element, event); }; } - + function createMouseEnterLeaveResponder(uid, eventName, handler) { return function(event) { var element = Event.cache[uid].element; - + Event.extend(event, element); var parent = event.relatedTarget; - + // Walk up the DOM tree to see if the related target is a descendant of // the original element. If it is, we ignore the event to match the // behavior of mouseenter/mouseleave. @@ -1367,12 +1376,12 @@ try { parent = parent.parentNode; } catch(e) { parent = element; } } - - if (parent === element) return; + + if (parent === element) return; handler.call(element, event); } } - + GLOBAL.Event._createResponder = createResponder; docEl = null; })(this); @@ -1380,23 +1389,23 @@ (function(GLOBAL) { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - + var TIMER; - + function fireContentLoadedEvent() { if (document.loaded) return; if (TIMER) window.clearTimeout(TIMER); document.loaded = true; document.fire('dom:loaded'); } - + function checkReadyState() { if (document.readyState === 'complete') { document.detachEvent('onreadystatechange', checkReadyState); fireContentLoadedEvent(); } } - + function pollDoScroll() { try { document.documentElement.doScroll('left'); @@ -1404,7 +1413,7 @@ TIMER = pollDoScroll.defer(); return; } - + fireContentLoadedEvent(); } @@ -1416,7 +1425,7 @@ fireContentLoadedEvent(); return; } - + if (document.addEventListener) { // All browsers that support DOM L2 Events support DOMContentLoaded, // including IE 9. @@ -1425,7 +1434,7 @@ document.attachEvent('onreadystatechange', checkReadyState); if (window == top) TIMER = pollDoScroll.defer(); } - + // Worst-case fallback. Event.observe(window, 'load', fireContentLoadedEvent); })(this); diff --git a/test/functional/event.html b/test/functional/event.html index 742c62bee..c9763400e 100644 --- a/test/functional/event.html +++ b/test/functional/event.html @@ -5,7 +5,7 @@ Prototype functional test file - + - + - +

Scope test - scope of the handler should be this element

- +

Event object test - should be present as a first argument

- +

Hijack link test (preventDefault)

- + - +

Mouse click: left middle right

- + - +

Context menu event (tries to prevent default)

- +

Event.element() test

- +

Event.currentTarget test

- + - +

Event.findElement() test

- + - +

Stop propagation test (bubbling)

- + - +

Keyup test - focus on the textarea and type

- + - +

bindAsEventListener() test

- + - +

Object.inspect(event) test

- + - +

mouseenter test

- + - +

Add unload events

- + - +
Event delegation
    @@ -294,8 +294,8 @@

    Prototype functional tests for the Event module

    Child 3 (mouseup)
- - Results: + + Results:
  • Test 1
  • @@ -303,7 +303,7 @@

    Prototype functional tests for the Event module

  • Test 3
- + - - + + - + - - + + From 1de5ab3812497c5adf9c3170519c64d6c7d09b81 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 17:20:00 -0500 Subject: [PATCH 428/502] A first pass on a complete rewrite of the unit tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not confident enough to get rid of the existing stuff, so the new folder is `test.new` and the new Rake tasks are under the `test_new` namespace. Use `rake test_new:start` to start the test server, and then visit the main URL to run all tests. Or add a comma-separated list of test suites (e.g., http://127.0.0.1:4567/test/number,string) to run only certain tests. There is a simple test runner, too, though I haven’t yet tested it on Windows or Linux. Run `rake test_new:run` to invoke the runner, using the same `BROWSERS` and `TESTS` environment variables that are used with the old `rake test`. --- test.new/config.ru | 2 + test.new/runner.rb | 313 ++ test.new/server.rb | 130 + test.new/static/css/mocha.css | 259 + test.new/static/fixtures/content.html | 1 + test.new/static/fixtures/data.json | 1 + test.new/static/fixtures/empty.html | 0 test.new/static/fixtures/hello.js | 1 + test.new/static/js/assertions.js | 472 ++ test.new/static/js/mocha.js | 5441 ++++++++++++++++++++ test.new/static/js/proclaim.js | 528 ++ test.new/static/js/test_helpers.js | 273 + test.new/tests/ajax.test.js | 482 ++ test.new/tests/array.old.test.js | 404 ++ test.new/tests/array.test.js | 359 ++ test.new/tests/base.test.js | 52 + test.new/tests/class.test.js | 223 + test.new/tests/date.test.js | 18 + test.new/tests/dom.test.js | 1615 ++++++ test.new/tests/element_mixins.test.js | 38 + test.new/tests/enumerable.test.js | 456 ++ test.new/tests/event.test.js | 294 ++ test.new/tests/event_handler.test.js | 103 + test.new/tests/form.test.js | 289 ++ test.new/tests/function.test.js | 214 + test.new/tests/hash.test.js | 240 + test.new/tests/layout.test.js | 483 ++ test.new/tests/number.test.js | 41 + test.new/tests/object.test.js | 231 + test.new/tests/periodical_executer.test.js | 38 + test.new/tests/position.test.js | 47 + test.new/tests/prototype.test.js | 49 + test.new/tests/range.test.js | 67 + test.new/tests/regexp.test.js | 46 + test.new/tests/selector.test.js | 413 ++ test.new/tests/selector_engine.test.js | 47 + test.new/tests/string.test.js | 571 ++ test.new/views/layout.erb | 86 + test.new/views/tests.erb | 9 + test.new/views/tests/ajax.erb | 2 + test.new/views/tests/array.erb | 1 + test.new/views/tests/dom.erb | 365 ++ test.new/views/tests/element_mixins.erb | 4 + test.new/views/tests/enumerable.erb | 8 + test.new/views/tests/event.erb | 4 + test.new/views/tests/event_handler.erb | 4 + test.new/views/tests/form.erb | 148 + test.new/views/tests/layout.erb | 302 ++ test.new/views/tests/object.erb | 6 + test.new/views/tests/position.erb | 9 + test.new/views/tests/selector.erb | 88 + test.new/views/tests/selector_engine.erb | 4 + 52 files changed, 15281 insertions(+) create mode 100644 test.new/config.ru create mode 100644 test.new/runner.rb create mode 100644 test.new/server.rb create mode 100644 test.new/static/css/mocha.css create mode 100644 test.new/static/fixtures/content.html create mode 100644 test.new/static/fixtures/data.json create mode 100644 test.new/static/fixtures/empty.html create mode 100644 test.new/static/fixtures/hello.js create mode 100644 test.new/static/js/assertions.js create mode 100644 test.new/static/js/mocha.js create mode 100644 test.new/static/js/proclaim.js create mode 100644 test.new/static/js/test_helpers.js create mode 100644 test.new/tests/ajax.test.js create mode 100644 test.new/tests/array.old.test.js create mode 100644 test.new/tests/array.test.js create mode 100644 test.new/tests/base.test.js create mode 100644 test.new/tests/class.test.js create mode 100644 test.new/tests/date.test.js create mode 100644 test.new/tests/dom.test.js create mode 100644 test.new/tests/element_mixins.test.js create mode 100644 test.new/tests/enumerable.test.js create mode 100644 test.new/tests/event.test.js create mode 100644 test.new/tests/event_handler.test.js create mode 100644 test.new/tests/form.test.js create mode 100644 test.new/tests/function.test.js create mode 100644 test.new/tests/hash.test.js create mode 100644 test.new/tests/layout.test.js create mode 100644 test.new/tests/number.test.js create mode 100644 test.new/tests/object.test.js create mode 100644 test.new/tests/periodical_executer.test.js create mode 100644 test.new/tests/position.test.js create mode 100644 test.new/tests/prototype.test.js create mode 100644 test.new/tests/range.test.js create mode 100644 test.new/tests/regexp.test.js create mode 100644 test.new/tests/selector.test.js create mode 100644 test.new/tests/selector_engine.test.js create mode 100644 test.new/tests/string.test.js create mode 100644 test.new/views/layout.erb create mode 100644 test.new/views/tests.erb create mode 100644 test.new/views/tests/ajax.erb create mode 100644 test.new/views/tests/array.erb create mode 100644 test.new/views/tests/dom.erb create mode 100644 test.new/views/tests/element_mixins.erb create mode 100644 test.new/views/tests/enumerable.erb create mode 100644 test.new/views/tests/event.erb create mode 100644 test.new/views/tests/event_handler.erb create mode 100644 test.new/views/tests/form.erb create mode 100644 test.new/views/tests/layout.erb create mode 100644 test.new/views/tests/object.erb create mode 100644 test.new/views/tests/position.erb create mode 100644 test.new/views/tests/selector.erb create mode 100644 test.new/views/tests/selector_engine.erb diff --git a/test.new/config.ru b/test.new/config.ru new file mode 100644 index 000000000..f0a7142f7 --- /dev/null +++ b/test.new/config.ru @@ -0,0 +1,2 @@ +require './app' +run UnitTests \ No newline at end of file diff --git a/test.new/runner.rb b/test.new/runner.rb new file mode 100644 index 000000000..3afc664b6 --- /dev/null +++ b/test.new/runner.rb @@ -0,0 +1,313 @@ +require 'pathname' +require 'sinatra/base' +require 'cgi' +require 'json' + +# A barebones runner for testing across multiple browsers quickly. +# +# Aims to be somewhat like the old unittest_js test runner, except that it's +# separate from the test server, and thus requires that the test server +# already be running. +# +# The fact that all unit tests can now run on one page means that we can +# simplify the test runner quite a bit. Here's what it does: +# +# 1. Based on the tests you told it to run (default: all tests), it generates +# a URL for running those tests on the test server. +# 2. It opens that URL in each of the browsers you specified (default: all +# browsers installed on your OS). +# 3. It spawns its own web server to listen for test results at a certain +# URL. The test server will hit that URL with the results (tests, passes, +# failures, duration) when the tests are done running. +# 4. It displays a summary of passes/failures in each browser. +# +# As with the old `rake test`, the `BROWSERS` and `TESTS` environment +# variables determine which browsers and tests you want to run. Multiple +# values should be space-separated. +# +# Additionally, the test runner supports a `GREP` environment variable that +# will be passed along to Mocha. Give it a pattern (with standard regex +# syntax) and Mocha will run only the tests whose names match that pattern. +# +# USAGE +# ----- +# +# rake test:run +# # (will run all tests in all browsers) +# +# rake test:run BROWSERS=safari,firefox TESTS=string,number +# # (will run string and number tests in only Safari and Firefox) +# +# rake test:run BROWSERS=chrome GREP=gsub +# # (will run all tests whose names contain "gsub" in only Chrome) + +module Runner + + module Browsers + + class Abstract + + def setup + end + + def teardown + end + + def supported? + true + end + + def host + require 'rbconfig' + RbConfig::CONFIG['host'] + end + + def macos? + host.include?('darwin') + end + + def windows? + host.include?('mswin') + end + + def linux? + host.include?('linux') + end + + def visit(url) + if windows? + system("#{path} #{url}") + elsif macos? + system("open -g -a '#{path}' '#{url}'") + elsif linux? + system("#{name} #{url}") + end + end + + def installed? + if macos? + installed = File.exists?(path) + else + true # TODO + end + end + + def name + n = self.class.name.split('::').last + linux? ? n.downcase : n + end + + def escaped_name + name.gsub(' ', '\ ') + end + + def path + if macos? + File.expand_path("/Applications/#{name}.app") + else + @path + end + end + end + + class Firefox < Abstract + + def initialize(path=File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe')) + @path = path + end + + def supported? + true + end + + def path + if windows? + Pathname.new('C:\Program Files').join( + 'Mozilla Firefox', 'firefox.exe') + else + super + end + end + + end + + class IE < Abstract + + def setup + require 'win32ole' if windows? + end + + def supported? + windows? + end + + def visit + ie = WIN32OLE.new('InternetExplorer.Application') + ie.visible = true + ie.Navigate(url) + end + + end + + class Safari < Abstract + + def supported? + macos? + end + + end + + class Chrome < Abstract + + def name + 'Google Chrome' + end + + end + + class Opera < Abstract + + def initialize(path='C:\Program Files\Opera\Opera.exe') + @path = path + end + + end + + end # Browsers + + BROWSERS = { + :ie => Browsers::IE, + :firefox => Browsers::Firefox, + :chrome => Browsers::Chrome, + :safari => Browsers::Safari, + :opera => Browsers::Opera + } + + # A Sinatra app that listens for test results. Because we've separated the + # runner from the test server, it listens on a different port. The test + # page will make a JSONP call when all the tests have been run. + class ResultsListener < Sinatra::Base + set :port, 4568 + + get '/results' do + results = { + :tests => params[:tests].to_i, + :passes => params[:passes].to_i, + :failures => params[:failures].to_i, + :duration => params[:duration].to_f + } + + pipe = Runner::write_pipe + pipe.write(JSON.dump(results) + "\n") + + # We don't even need to render anything; the test page doesn't care + # about a response. + end + end + + class << self + + attr_accessor :read_pipe, :write_pipe + + def run(browsers=nil, tests=nil, grep=nil) + @browsers = browsers.nil? ? BROWSERS.keys : + browsers.split(/\s*,\s*/).map(&:to_sym) + @tests = tests.nil? ? [] : tests.split(/\s*,\s*/).map(&:to_sym) + @grep = grep + + @browsers = @browsers.map { |b| get_browser(b).new } + + @url = %Q[http://127.0.0.1:4567/test/#{@tests.join(',')}?results_url=#{results_url}] + + if @grep && !@grep.nil? && !@grep.empty? + @url << "&grep=#{CGI::escape(@grep)}" + end + + Runner::read_pipe, Runner::write_pipe = IO.pipe + + # Start up the Sinatra app to listen for test results, but do it in a + # fork because it sends some output to stdout and stderr that is + # irrelevant and annoying. + pid = fork do + Runner::read_pipe.close + STDOUT.reopen('/dev/null', 'w') + STDERR.reopen('/dev/null', 'w') + + ResultsListener.run! + end + + Runner::write_pipe.close + + # Make sure we clean up the forked process when we're done. + at_exit do + Process.kill(9, pid) + Process.wait(pid) + end + + trap('INT') { exit } + + results_table = {} + + @browsers.each do |browser| + if !browser.supported? + puts "Skipping #{browser.name} (not supported on this OS)" + next + end + if !browser.installed? + puts "Skipping #{browser.name} (not installed on this OS)" + next + end + print "Running in #{browser.name}... " + + browser.setup + browser.visit(@url) + browser.teardown + + message = Runner::read_pipe.gets + results = JSON.parse(message) + results_table[browser.name] = results + + puts "done." + end + + puts "\n\n" + + results_table.each do |k, v| + puts "Results for #{k}:" + report_results(v) + end + end + + def results_url + "http://127.0.0.1:4568/results" + end + + def get_browser(name) + BROWSERS[name] + end + + def report_results(results) + t, p, f, d = [ + results["tests"], + results["passes"], + results["failures"], + results["duration"] + ] + + summary = [ + "#{t} #{plural(t, 'test')}", + "#{p} #{plural(p, 'pass', 'passes')}", + "#{f} #{plural(f, 'failure')}" + ] + + puts %Q[#{summary.join(', ')} in #{d} #{plural(d, 'second')}\n] + end + + def plural(num, singular, plural=nil) + plural = "#{singular}s" if plural.nil? + num == 1 ? singular : plural + end + + end + +end \ No newline at end of file diff --git a/test.new/server.rb b/test.new/server.rb new file mode 100644 index 000000000..cf07865e1 --- /dev/null +++ b/test.new/server.rb @@ -0,0 +1,130 @@ +require 'sinatra/base' +require 'pathname' +require 'json' + +require 'pp' + +class UnitTests < Sinatra::Application + + PWD = Pathname.new( File.expand_path( File.dirname(__FILE__) ) ) + + set :root, PWD + set :public_folder, PWD.join('static') + + PATH_TO_PROTOTYPE = PWD.join('..', 'dist', 'prototype.js') + + unless PATH_TO_PROTOTYPE.file? + raise "You must run `rake dist` before starting the server." + end + + PATH_TO_TEST_JS = PWD.join('tests') + + + SUITES = [] + + PATH_TO_TEST_JS.each_entry do |e| + next if e.directory? + basename = e.basename('.*').to_s + next if basename.start_with?('.') + SUITES << basename.sub('.test', '') + end + + SUITES_WITH_VIEWS = [] + + PWD.join('views', 'tests').each_entry do |e| + next if e.directory? + basename = e.basename('.*').to_s + SUITES_WITH_VIEWS << basename + end + + + def self.get_or_post(url, &block) + get(url, &block) + post(url, &block) + end + + get '/test/:names?' do + names = params[:names] + @suites = names.nil? ? SUITES : names.split(/,/).uniq + erb :tests, :locals => { :suites => @suites } + end + + get '/prototype.js' do + content_type 'text/javascript' + send_file PATH_TO_PROTOTYPE + end + + get '/js/tests/:filename' do + filename = params[:filename] + path = PATH_TO_TEST_JS.join(filename) + if path.file? + content_type 'text/javascript' + send_file PATH_TO_TEST_JS.join(filename) + else + status 404 + end + end + + + # Routes for Ajax tests + + get_or_post '/inspect' do + response = { + :headers => request_headers(request.env), + :method => request.request_method, + :body => request.body.read + } + + pp response[:headers] + + content_type 'application/json' + JSON.dump(response) + end + + get '/response' do + header_params = {} + params.each do |k, v| + v = v.gsub(/[\r\n]/, '') + header_params[k] = v + end + headers(header_params) + + if params[:'Content-Type'] + content_type params[:'Content-Type'].strip + else + content_type 'application/json' + end + + params[:responseBody] || "" + end + + # Collect all the headers that were sent with a request. (This is harder than + # it seems because of how Rack normalizes headers.) + def request_headers(env) + results = {} + + env.each do |k, v| + next unless k.start_with?('HTTP_') || k == 'CONTENT_TYPE' + key = k.sub('HTTP_', '').gsub('_', '-').downcase + results[key] = v + end + + results + end + + + not_found do + "File not found." + end + + + helpers do + + def suite_has_html?(suite) + SUITES_WITH_VIEWS.include?(suite) + end + + end + + # run! if app_file == $0 +end \ No newline at end of file diff --git a/test.new/static/css/mocha.css b/test.new/static/css/mocha.css new file mode 100644 index 000000000..db0e388b9 --- /dev/null +++ b/test.new/static/css/mocha.css @@ -0,0 +1,259 @@ +@charset "utf-8"; + +body { + margin:0; +} + +#mocha { + font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 60px 50px; +} + +#mocha ul, #mocha li { + margin: 0; + padding: 0; +} + +#mocha ul { + list-style: none; +} + +#mocha h1, #mocha h2 { + margin: 0; +} + +#mocha h1 { + margin-top: 15px; + font-size: 1em; + font-weight: 200; +} + +#mocha h1 a { + text-decoration: none; + color: inherit; +} + +#mocha h1 a:hover { + text-decoration: underline; +} + +#mocha .suite .suite h1 { + margin-top: 0; + font-size: .8em; +} + +#mocha .hidden { + display: none; +} + +#mocha h2 { + font-size: 12px; + font-weight: normal; + cursor: pointer; +} + +#mocha .suite { + margin-left: 15px; +} + +#mocha .test { + margin-left: 15px; + overflow: hidden; +} + +#mocha .test.pending:hover h2::after { + content: '(pending)'; + font-family: arial, sans-serif; +} + +#mocha .test.pass.medium .duration { + background: #C09853; +} + +#mocha .test.pass.slow .duration { + background: #B94A48; +} + +#mocha .test.pass::before { + content: '✓'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #00d6b2; +} + +#mocha .test.pass .duration { + font-size: 9px; + margin-left: 5px; + padding: 2px 5px; + color: white; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + box-shadow: inset 0 1px 1px rgba(0,0,0,.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; +} + +#mocha .test.pass.fast .duration { + display: none; +} + +#mocha .test.pending { + color: #0b97c4; +} + +#mocha .test.pending::before { + content: '◦'; + color: #0b97c4; +} + +#mocha .test.fail { + color: #c00; +} + +#mocha .test.fail pre { + color: black; +} + +#mocha .test.fail::before { + content: '✖'; + font-size: 12px; + display: block; + float: left; + margin-right: 5px; + color: #c00; +} + +#mocha .test pre.error { + color: #c00; + max-height: 300px; + overflow: auto; +} + +#mocha .test pre { + display: block; + float: left; + clear: left; + font: 12px/1.5 monaco, monospace; + margin: 5px; + padding: 15px; + border: 1px solid #eee; + border-bottom-color: #ddd; + -webkit-border-radius: 3px; + -webkit-box-shadow: 0 1px 3px #eee; + -moz-border-radius: 3px; + -moz-box-shadow: 0 1px 3px #eee; +} + +#mocha .test h2 { + position: relative; +} + +#mocha .test a.replay { + position: absolute; + top: 3px; + right: 0; + text-decoration: none; + vertical-align: middle; + display: block; + width: 15px; + height: 15px; + line-height: 15px; + text-align: center; + background: #eee; + font-size: 15px; + -moz-border-radius: 15px; + border-radius: 15px; + -webkit-transition: opacity 200ms; + -moz-transition: opacity 200ms; + transition: opacity 200ms; + opacity: 0.3; + color: #888; +} + +#mocha .test:hover a.replay { + opacity: 1; +} + +#mocha-report.pass .test.fail { + display: none; +} + +#mocha-report.fail .test.pass { + display: none; +} + +#mocha-report.pending .test.pass +#mocha-report.pending .test.fail, { + display: none; +} +#mocha-report.pending .test.pass.pending { + display: block; +} + +#mocha-error { + color: #c00; + font-size: 1.5em; + font-weight: 100; + letter-spacing: 1px; +} + +#mocha-stats { + position: fixed; + top: 15px; + right: 10px; + font-size: 12px; + margin: 0; + color: #888; + z-index: 1; +} + +#mocha-stats .progress { + float: right; + padding-top: 0; +} + +#mocha-stats em { + color: black; +} + +#mocha-stats a { + text-decoration: none; + color: inherit; +} + +#mocha-stats a:hover { + border-bottom: 1px solid #eee; +} + +#mocha-stats li { + display: inline-block; + margin: 0 5px; + list-style: none; + padding-top: 11px; +} + +#mocha-stats canvas { + width: 40px; + height: 40px; +} + +#mocha code .comment { color: #ddd } +#mocha code .init { color: #2F6FAD } +#mocha code .string { color: #5890AD } +#mocha code .keyword { color: #8A6343 } +#mocha code .number { color: #2F6FAD } + +@media screen and (max-device-width: 480px) { + #mocha { + margin: 60px 0px; + } + + #mocha #stats { + position: absolute; + } +} \ No newline at end of file diff --git a/test.new/static/fixtures/content.html b/test.new/static/fixtures/content.html new file mode 100644 index 000000000..ee3701d3c --- /dev/null +++ b/test.new/static/fixtures/content.html @@ -0,0 +1 @@ +Pack my box with five dozen liquor jugs! Oh, how quickly daft jumping zebras vex... \ No newline at end of file diff --git a/test.new/static/fixtures/data.json b/test.new/static/fixtures/data.json new file mode 100644 index 000000000..d1d0f5cbe --- /dev/null +++ b/test.new/static/fixtures/data.json @@ -0,0 +1 @@ +{"test": 123} \ No newline at end of file diff --git a/test.new/static/fixtures/empty.html b/test.new/static/fixtures/empty.html new file mode 100644 index 000000000..e69de29bb diff --git a/test.new/static/fixtures/hello.js b/test.new/static/fixtures/hello.js new file mode 100644 index 000000000..63d81117b --- /dev/null +++ b/test.new/static/fixtures/hello.js @@ -0,0 +1 @@ +$("content").update("

Hello world!

"); diff --git a/test.new/static/js/assertions.js b/test.new/static/js/assertions.js new file mode 100644 index 000000000..a56b5e4a5 --- /dev/null +++ b/test.new/static/js/assertions.js @@ -0,0 +1,472 @@ + +(function () { + + function ok(val, message) { + if (!!!val) { + fail(val, true, message, '=='); + } + } + + function buildMessage() { + var args = $A(arguments), template = args.shift(); + return template.interpolate(args); + } + + function fail (actual, expected, message, operator, stackStartFunction) { + throw new AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); + } + + // Utility for deep equality testing of objects + function objectsEqual (obj1, obj2) { + /* jshint eqeqeq: false */ + + // Check for undefined or null + if (isUndefinedOrNull(obj1) || isUndefinedOrNull(obj2)) { + return false; + } + + // Object prototypes must be the same + if (obj1.prototype !== obj2.prototype) { + return false; + } + + // Handle argument objects + if (isArgumentsObject(obj1)) { + if (!isArgumentsObject(obj2)) { + return false; + } + obj1 = Array.prototype.slice.call(obj1); + obj2 = Array.prototype.slice.call(obj2); + } + + // Check number of own properties + var obj1Keys = getObjectKeys(obj1); + var obj2Keys = getObjectKeys(obj2); + if (obj1Keys.length !== obj2Keys.length) { + return false; + } + + obj1Keys.sort(); + obj2Keys.sort(); + + // Cheap initial key test (see https://github.com/joyent/node/blob/master/lib/assert.js) + var key, i, len = obj1Keys.length; + for (i = 0; i < len; i += 1) { + if (obj1Keys[i] != obj2Keys[i]) { + return false; + } + } + + // Expensive deep test + for (i = 0; i < len; i += 1) { + key = obj1Keys[i]; + if (!isDeepEqual(obj1[key], obj2[key])) { + return false; + } + } + + // If it got this far... + return true; + } + + // Utility for deep equality testing + function isDeepEqual (actual, expected) { + /* jshint eqeqeq: false */ + if (actual === expected) { + return true; + } + if (expected instanceof Date && actual instanceof Date) { + return actual.getTime() === expected.getTime(); + } + if (actual instanceof RegExp && expected instanceof RegExp) { + return ( + actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase + ); + } + if (typeof actual !== 'object' && typeof expected !== 'object') { + return actual == expected; + } + return objectsEqual(actual, expected); + } + + // Utility for testing whether a function throws an error + function functionThrows (fn, expected) { + + // Try/catch + var thrown = false; + var thrownError; + try { + fn(); + } catch (err) { + thrown = true; + thrownError = err; + } + + // Check error + if (thrown && expected) { + thrown = errorMatches(thrownError, expected); + } + + return thrown; + } + + // Utility for checking whether an error matches a given constructor, regexp or string + function errorMatches (actual, expected) { + if (typeof expected === 'string') { + return actual.message === expected; + } + if (expected instanceof RegExp) { + return expected.test(actual.message); + } + if (actual instanceof expected) { + return true; + } + return false; + } + + function AssertionError (opts) { + opts = opts || {}; + + this.name = 'AssertionError'; + this.actual = opts.actual; + this.expected = opts.expected; + this.operator = opts.operator || ''; + this.message = opts.message; + + if (!this.message) { + this.message = this.toString(); + } + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, opts.stackStartFunction || fail); + } + } + + AssertionError.prototype = (Object.create ? Object.create(Error.prototype) : new Error()); + AssertionError.prototype.name = 'AssertionError'; + AssertionError.prototype.constructor = AssertionError; + + // Assertion error to string + AssertionError.prototype.toString = function () { + if (this.message) { + return this.name + ': ' +this.message; + } else { + return this.name + ': ' + + this.actual + ' ' + + this.operator + ' ' + + this.expected; + } + }; + + + var assert = ok; + + Object.extend(assert, { + + equal: function (actual, expected, message) { + if (actual != expected) { + var msg = buildMessage(message, 'expected "#{0}" to equal "#{1}"', + actual, expected); + fail(actual, expected, msg, '=='); + } + }, + + notEqual: function (actual, expected, message) { + if (actual == expected) { + var msg = buildMessage(message, 'expected "#{0}" not to equal "#{1}"', + actual, expected); + fail(actual, expected, msg, '!='); + } + }, + + strictEqual: function (actual, expected, message) { + if (actual !== expected) { + var msg = buildMessage( + message, + 'expected "#{0}" to strictly equal "#{1}"', + actual, + expected + ); + fail(actual, expected, msg, '==='); + } + }, + + notStrictEqual: function (actual, expected, message) { + if (actual === expected) { + var msg = buildMessage( + message, + 'expected "#{0}" not to strictly equal "#{1}"', + actual, + expected + ); + fail(actual, expected, msg, '!='); + } + }, + + deepEqual: function (actual, expected, message) { + if (!isDeepEqual(actual, expected)) { + var msg = buildMessage( + message, + 'expected #{0} to deep-equal #{1}', + actual, + expected + ); + fail(actual, expected, msg, 'deepEqual'); + } + }, + + notDeepEqual: function (actual, expected, message) { + if (isDeepEqual(actual, expected)) { + var msg = buildMessage( + message, + 'expected #{0} not to deep-equal #{1}', + actual, + expected + ); + fail(actual, expected, msg, 'notDeepEqual'); + } + }, + + 'throws': function (fn, expected, message) { + if (!functionThrows(fn, expected)) { + var msg = buildMessage( + message, + 'expected function to throw #{0}', + expected || 'error' + ); + fail(val, true, msg, 'throws'); + } + }, + + doesNotThrow: function (fn, expected, message) { + if (functionThrows(fn, expected)) { + var msg = buildMessage( + message, + 'expected function not to throw #{0}', + expected || 'error' + ); + } + }, + + isTypeOf: function (val, type, message) { + if (typeof val !== type) { + var msg = buildMessage( + message, + 'expected "#{0}" to be of type #{1}', + val, type + ); + fail(val, type, msg, 'isTypeOf'); + } + }, + + isNotTypeOf: function (val, type, message) { + if (typeof val === type) { + var msg = buildMessage( + message, + 'expected "#{0}" not to be of type #{1}', + val, type + ); + fail(val, type, msg, 'isNotTypeOf'); + } + }, + + isInstanceOf: function (val, constructor, message) { + if (!(val instanceof constructor)) { + var msg = buildMessage( + message, + 'expected #{0} to be an instance of #{1}', + val, constructor + ); + fail(val, constructor, msg, 'instanceof'); + } + }, + + isNotInstanceOf: function (val, constructor, message) { + if (val instanceof constructor) { + var msg = buildMessage( + message, + 'expected #{0} not to be an instance of #{1}', + val, constructor + ); + fail(val, constructor, msg, '!instanceof'); + } + }, + + isNull: function (val, message) { + if (val !== null) { + var msg = buildMessage( + message, + 'expected #{0} to be null', + val + ); + fail(val, null, msg, 'isNull'); + } + }, + + isNotNull: function (val, message) { + if (val === null) { + var msg = buildMessage( + message, + 'expected #{0} not to be null', + val + ); + fail(val, null, msg, 'isNotNull'); + } + }, + + isUndefined: function (val, message) { + var undef; + if (typeof val !== 'undefined') { + var msg = buildMessage( + message, + 'expected #{0} to be undefined', + val + ); + fail(val, undef, msg, 'isUndefined'); + } + }, + + isDefined: function (val, message) { + var undef; + if (typeof val === 'undefined') { + var msg = buildMessage( + message, + 'expected #{0} to be defined', + val + ); + fail(val, undef, msg, 'isDefined'); + } + }, + + match: function (actual, expected, message) { + if (!expected.test(actual)) { + var msg = buildMessage( + message, + 'expected #{0} to match #{1}', + actual, + expected + ); + fail(actual, expected, msg, 'match'); + } + }, + + notMatch: function (actual, expected, message) { + if (expected.test(actual)) { + var msg = buildMessage( + message, + 'expected #{0} not to match #{1}', + actual, + expected + ); + fail(actual, expected, msg, '!match'); + } + }, + + enumEqual: function (expected, actual, message) { + expected = $A(expected); + actual = $A(actual); + + var passes = expected.length == actual.length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1]; }); + + if (!passes) { + var msg = buildMessage( + message, + 'expected collection #{0} to match collection #{1}', + actual, + expected + ); + fail(actual, expected, msg, 'enumEqual'); + } + }, + + enabled: function () { + for (var i = 0, element; element = arguments[i]; i++) { + assert( + !$(element).disabled, + 'element was disabled: ' + Object.inspect(element) + ); + } + }, + + disabled: function () { + for (var i = 0, element; element = arguments[i]; i++) { + assert( + $(element).disabled, + 'element was enabled: ' + Object.inspect(element) + ); + } + }, + + respondsTo: function (method, obj, message) { + var passes = (method in obj) && (typeof obj[method] === 'function'); + + if (!passes) { + var msg = buildMessage( + message || 'assert.respondsTo', + 'expected #{0} to respond to method "#{1}"', + obj, + method + ); + fail(obj, method, msg, 'respondsTo'); + } + }, + + elementsMatch: function () { + var message, passes = true, expressions = $A(arguments), elements = $A(expressions.shift()); + + if (elements.length !== expressions.length) { + passes = false; + message = 'Size mismatch: #{0} elements, #{1} expressions (#{2})'.interpolate( + [elements.length, expressions.length, expressions]); + } else { + elements.zip(expressions).all(function (pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + + message = 'in index <#{0}>: expected <#{1}> but got #{2}'.interpolate( + [index, expression, Object.inspect(element)]); + passes = false; + }); + } + + assert(passes, message); + }, + + elementMatches: function (element, expression, message) { + assert.elementsMatch([element], expression); + } + + }); + + + + // Exports + // ------- + + // AMD + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return assert; + }); + } + // CommonJS + else if (typeof module !== 'undefined' && module.exports) { + module.exports = assert; + } + // Script tag + else { + root.assert = assert; + } + +})(this); \ No newline at end of file diff --git a/test.new/static/js/mocha.js b/test.new/static/js/mocha.js new file mode 100644 index 000000000..41f89a7d6 --- /dev/null +++ b/test.new/static/js/mocha.js @@ -0,0 +1,5441 @@ +;(function(){ + +// CommonJS require() + +function require(p){ + var path = require.resolve(p) + , mod = require.modules[path]; + if (!mod) throw new Error('failed to require "' + p + '"'); + if (!mod.exports) { + mod.exports = {}; + mod.call(mod.exports, mod, mod.exports, require.relative(path)); + } + return mod.exports; + } + +require.modules = {}; + +require.resolve = function (path){ + var orig = path + , reg = path + '.js' + , index = path + '/index.js'; + return require.modules[reg] && reg + || require.modules[index] && index + || orig; + }; + +require.register = function (path, fn){ + require.modules[path] = fn; + }; + +require.relative = function (parent) { + return function(p){ + if ('.' != p.charAt(0)) return require(p); + + var path = parent.split('/') + , segs = p.split('/'); + path.pop(); + + for (var i = 0; i < segs.length; i++) { + var seg = segs[i]; + if ('..' == seg) path.pop(); + else if ('.' != seg) path.push(seg); + } + + return require(path.join('/')); + }; + }; + + +require.register("browser/debug.js", function(module, exports, require){ + +module.exports = function(type){ + return function(){ + } +}; + +}); // module: browser/debug.js + +require.register("browser/diff.js", function(module, exports, require){ +/* See license.txt for terms of usage */ + +/* + * Text diff implementation. + * + * This library supports the following APIS: + * JsDiff.diffChars: Character by character diff + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace + * JsDiff.diffLines: Line based diff + * + * JsDiff.diffCss: Diff targeted at CSS content + * + * These methods are based on the implementation proposed in + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 + */ +var JsDiff = (function() { + function clonePath(path) { + return { newPos: path.newPos, components: path.components.slice(0) }; + } + function removeEmpty(array) { + var ret = []; + for (var i = 0; i < array.length; i++) { + if (array[i]) { + ret.push(array[i]); + } + } + return ret; + } + function escapeHTML(s) { + var n = s; + n = n.replace(/&/g, "&"); + n = n.replace(//g, ">"); + n = n.replace(/"/g, """); + + return n; + } + + + var fbDiff = function(ignoreWhitespace) { + this.ignoreWhitespace = ignoreWhitespace; + }; + fbDiff.prototype = { + diff: function(oldString, newString) { + // Handle the identity case (this is due to unrolling editLength == 0 + if (newString == oldString) { + return [{ value: newString }]; + } + if (!newString) { + return [{ value: oldString, removed: true }]; + } + if (!oldString) { + return [{ value: newString, added: true }]; + } + + newString = this.tokenize(newString); + oldString = this.tokenize(oldString); + + var newLen = newString.length, oldLen = oldString.length; + var maxEditLength = newLen + oldLen; + var bestPath = [{ newPos: -1, components: [] }]; + + // Seed editLength = 0 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { + return bestPath[0].components; + } + + for (var editLength = 1; editLength <= maxEditLength; editLength++) { + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { + var basePath; + var addPath = bestPath[diagonalPath-1], + removePath = bestPath[diagonalPath+1]; + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; + if (addPath) { + // No one else is going to attempt to use this value, clear it + bestPath[diagonalPath-1] = undefined; + } + + var canAdd = addPath && addPath.newPos+1 < newLen; + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; + if (!canAdd && !canRemove) { + bestPath[diagonalPath] = undefined; + continue; + } + + // Select the diagonal that we want to branch from. We select the prior + // path whose position in the new string is the farthest from the origin + // and does not pass the bounds of the diff graph + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { + basePath = clonePath(removePath); + this.pushComponent(basePath.components, oldString[oldPos], undefined, true); + } else { + basePath = clonePath(addPath); + basePath.newPos++; + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); + } + + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); + + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { + return basePath.components; + } else { + bestPath[diagonalPath] = basePath; + } + } + } + }, + + pushComponent: function(components, value, added, removed) { + var last = components[components.length-1]; + if (last && last.added === added && last.removed === removed) { + // We need to clone here as the component clone operation is just + // as shallow array clone + components[components.length-1] = + {value: this.join(last.value, value), added: added, removed: removed }; + } else { + components.push({value: value, added: added, removed: removed }); + } + }, + extractCommon: function(basePath, newString, oldString, diagonalPath) { + var newLen = newString.length, + oldLen = oldString.length, + newPos = basePath.newPos, + oldPos = newPos - diagonalPath; + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { + newPos++; + oldPos++; + + this.pushComponent(basePath.components, newString[newPos], undefined, undefined); + } + basePath.newPos = newPos; + return oldPos; + }, + + equals: function(left, right) { + var reWhitespace = /\S/; + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { + return true; + } else { + return left == right; + } + }, + join: function(left, right) { + return left + right; + }, + tokenize: function(value) { + return value; + } + }; + + var CharDiff = new fbDiff(); + + var WordDiff = new fbDiff(true); + WordDiff.tokenize = function(value) { + return removeEmpty(value.split(/(\s+|\b)/)); + }; + + var CssDiff = new fbDiff(true); + CssDiff.tokenize = function(value) { + return removeEmpty(value.split(/([{}:;,]|\s+)/)); + }; + + var LineDiff = new fbDiff(); + LineDiff.tokenize = function(value) { + return value.split(/^/m); + }; + + return { + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, + + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, + + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { + var ret = []; + + ret.push("Index: " + fileName); + ret.push("==================================================================="); + ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); + ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); + + var diff = LineDiff.diff(oldStr, newStr); + if (!diff[diff.length-1].value) { + diff.pop(); // Remove trailing newline add + } + diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier + + function contextLines(lines) { + return lines.map(function(entry) { return ' ' + entry; }); + } + function eofNL(curRange, i, current) { + var last = diff[diff.length-2], + isLast = i === diff.length-2, + isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); + + // Figure out if this is the last line for the given file and missing NL + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { + curRange.push('\\ No newline at end of file'); + } + } + + var oldRangeStart = 0, newRangeStart = 0, curRange = [], + oldLine = 1, newLine = 1; + for (var i = 0; i < diff.length; i++) { + var current = diff[i], + lines = current.lines || current.value.replace(/\n$/, "").split("\n"); + current.lines = lines; + + if (current.added || current.removed) { + if (!oldRangeStart) { + var prev = diff[i-1]; + oldRangeStart = oldLine; + newRangeStart = newLine; + + if (prev) { + curRange = contextLines(prev.lines.slice(-4)); + oldRangeStart -= curRange.length; + newRangeStart -= curRange.length; + } + } + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); + eofNL(curRange, i, current); + + if (current.added) { + newLine += lines.length; + } else { + oldLine += lines.length; + } + } else { + if (oldRangeStart) { + // Close out any changes that have been output (or join overlapping) + if (lines.length <= 8 && i < diff.length-2) { + // Overlapping + curRange.push.apply(curRange, contextLines(lines)); + } else { + // end the range and output + var contextSize = Math.min(lines.length, 4); + ret.push( + "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) + + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) + + " @@"); + ret.push.apply(ret, curRange); + ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); + if (lines.length <= 4) { + eofNL(ret, i, current); + } + + oldRangeStart = 0; newRangeStart = 0; curRange = []; + } + } + oldLine += lines.length; + newLine += lines.length; + } + } + + return ret.join('\n') + '\n'; + }, + + convertChangesToXML: function(changes){ + var ret = []; + for ( var i = 0; i < changes.length; i++) { + var change = changes[i]; + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + + ret.push(escapeHTML(change.value)); + + if (change.added) { + ret.push(""); + } else if (change.removed) { + ret.push(""); + } + } + return ret.join(""); + } + }; +})(); + +if (typeof module !== "undefined") { + module.exports = JsDiff; +} + +}); // module: browser/diff.js + +require.register("browser/events.js", function(module, exports, require){ + +/** + * Module exports. + */ + +exports.EventEmitter = EventEmitter; + +/** + * Check if `obj` is an array. + */ + +function isArray(obj) { + return '[object Array]' == {}.toString.call(obj); +} + +/** + * Event emitter constructor. + * + * @api public + */ + +function EventEmitter(){}; + +/** + * Adds a listener. + * + * @api public + */ + +EventEmitter.prototype.on = function (name, fn) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = fn; + } else if (isArray(this.$events[name])) { + this.$events[name].push(fn); + } else { + this.$events[name] = [this.$events[name], fn]; + } + + return this; +}; + +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +/** + * Adds a volatile listener. + * + * @api public + */ + +EventEmitter.prototype.once = function (name, fn) { + var self = this; + + function on () { + self.removeListener(name, on); + fn.apply(this, arguments); + }; + + on.listener = fn; + this.on(name, on); + + return this; +}; + +/** + * Removes a listener. + * + * @api public + */ + +EventEmitter.prototype.removeListener = function (name, fn) { + if (this.$events && this.$events[name]) { + var list = this.$events[name]; + + if (isArray(list)) { + var pos = -1; + + for (var i = 0, l = list.length; i < l; i++) { + if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { + pos = i; + break; + } + } + + if (pos < 0) { + return this; + } + + list.splice(pos, 1); + + if (!list.length) { + delete this.$events[name]; + } + } else if (list === fn || (list.listener && list.listener === fn)) { + delete this.$events[name]; + } + } + + return this; +}; + +/** + * Removes all listeners for an event. + * + * @api public + */ + +EventEmitter.prototype.removeAllListeners = function (name) { + if (name === undefined) { + this.$events = {}; + return this; + } + + if (this.$events && this.$events[name]) { + this.$events[name] = null; + } + + return this; +}; + +/** + * Gets all listeners for a certain event. + * + * @api public + */ + +EventEmitter.prototype.listeners = function (name) { + if (!this.$events) { + this.$events = {}; + } + + if (!this.$events[name]) { + this.$events[name] = []; + } + + if (!isArray(this.$events[name])) { + this.$events[name] = [this.$events[name]]; + } + + return this.$events[name]; +}; + +/** + * Emits an event. + * + * @api public + */ + +EventEmitter.prototype.emit = function (name) { + if (!this.$events) { + return false; + } + + var handler = this.$events[name]; + + if (!handler) { + return false; + } + + var args = [].slice.call(arguments, 1); + + if ('function' == typeof handler) { + handler.apply(this, args); + } else if (isArray(handler)) { + var listeners = handler.slice(); + + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + } else { + return false; + } + + return true; +}; +}); // module: browser/events.js + +require.register("browser/fs.js", function(module, exports, require){ + +}); // module: browser/fs.js + +require.register("browser/path.js", function(module, exports, require){ + +}); // module: browser/path.js + +require.register("browser/progress.js", function(module, exports, require){ + +/** + * Expose `Progress`. + */ + +module.exports = Progress; + +/** + * Initialize a new `Progress` indicator. + */ + +function Progress() { + this.percent = 0; + this.size(0); + this.fontSize(11); + this.font('helvetica, arial, sans-serif'); +} + +/** + * Set progress size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.size = function(n){ + this._size = n; + return this; +}; + +/** + * Set text to `str`. + * + * @param {String} str + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.text = function(str){ + this._text = str; + return this; +}; + +/** + * Set font size to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + * @api public + */ + +Progress.prototype.fontSize = function(n){ + this._fontSize = n; + return this; +}; + +/** + * Set font `family`. + * + * @param {String} family + * @return {Progress} for chaining + */ + +Progress.prototype.font = function(family){ + this._font = family; + return this; +}; + +/** + * Update percentage to `n`. + * + * @param {Number} n + * @return {Progress} for chaining + */ + +Progress.prototype.update = function(n){ + this.percent = n; + return this; +}; + +/** + * Draw on `ctx`. + * + * @param {CanvasRenderingContext2d} ctx + * @return {Progress} for chaining + */ + +Progress.prototype.draw = function(ctx){ + var percent = Math.min(this.percent, 100) + , size = this._size + , half = size / 2 + , x = half + , y = half + , rad = half - 1 + , fontSize = this._fontSize; + + ctx.font = fontSize + 'px ' + this._font; + + var angle = Math.PI * 2 * (percent / 100); + ctx.clearRect(0, 0, size, size); + + // outer circle + ctx.strokeStyle = '#9f9f9f'; + ctx.beginPath(); + ctx.arc(x, y, rad, 0, angle, false); + ctx.stroke(); + + // inner circle + ctx.strokeStyle = '#eee'; + ctx.beginPath(); + ctx.arc(x, y, rad - 1, 0, angle, true); + ctx.stroke(); + + // text + var text = this._text || (percent | 0) + '%' + , w = ctx.measureText(text).width; + + ctx.fillText( + text + , x - w / 2 + 1 + , y + fontSize / 2 - 1); + + return this; +}; + +}); // module: browser/progress.js + +require.register("browser/tty.js", function(module, exports, require){ + +exports.isatty = function(){ + return true; +}; + +exports.getWindowSize = function(){ + if ('innerHeight' in global) { + return [global.innerHeight, global.innerWidth]; + } else { + // In a Web Worker, the DOM Window is not available. + return [640, 480]; + } +}; + +}); // module: browser/tty.js + +require.register("context.js", function(module, exports, require){ + +/** + * Expose `Context`. + */ + +module.exports = Context; + +/** + * Initialize a new `Context`. + * + * @api private + */ + +function Context(){} + +/** + * Set or get the context `Runnable` to `runnable`. + * + * @param {Runnable} runnable + * @return {Context} + * @api private + */ + +Context.prototype.runnable = function(runnable){ + if (0 == arguments.length) return this._runnable; + this.test = this._runnable = runnable; + return this; +}; + +/** + * Set test timeout `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.timeout = function(ms){ + this.runnable().timeout(ms); + return this; +}; + +/** + * Set test slowness threshold `ms`. + * + * @param {Number} ms + * @return {Context} self + * @api private + */ + +Context.prototype.slow = function(ms){ + this.runnable().slow(ms); + return this; +}; + +/** + * Inspect the context void of `._runnable`. + * + * @return {String} + * @api private + */ + +Context.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_runnable' == key) return; + if ('test' == key) return; + return val; + }, 2); +}; + +}); // module: context.js + +require.register("hook.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Hook`. + */ + +module.exports = Hook; + +/** + * Initialize a new `Hook` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Hook(title, fn) { + Runnable.call(this, title, fn); + this.type = 'hook'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Hook.prototype = new F; +Hook.prototype.constructor = Hook; + + +/** + * Get or set the test `err`. + * + * @param {Error} err + * @return {Error} + * @api public + */ + +Hook.prototype.error = function(err){ + if (0 == arguments.length) { + var err = this._error; + this._error = null; + return err; + } + + this._error = err; +}; + +}); // module: hook.js + +require.register("interfaces/bdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * BDD-style interface: + * + * describe('Array', function(){ + * describe('#indexOf()', function(){ + * it('should return -1 when not present', function(){ + * + * }); + * + * it('should return the index when present', function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.describe = context.context = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending describe. + */ + + context.xdescribe = + context.xcontext = + context.describe.skip = function(title, fn){ + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive suite. + */ + + context.describe.only = function(title, fn){ + var suite = context.describe(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.it = context.specify = function(title, fn){ + var suite = suites[0]; + if (suite.pending) var fn = null; + var test = new Test(title, fn); + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.it.only = function(title, fn){ + var test = context.it(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.xit = + context.xspecify = + context.it.skip = function(title){ + context.it(title); + }; + }); +}; + +}); // module: interfaces/bdd.js + +require.register("interfaces/exports.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * exports.Array = { + * '#indexOf()': { + * 'should return -1 when the value is not present': function(){ + * + * }, + * + * 'should return the correct index when the value is present': function(){ + * + * } + * } + * }; + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('require', visit); + + function visit(obj) { + var suite; + for (var key in obj) { + if ('function' == typeof obj[key]) { + var fn = obj[key]; + switch (key) { + case 'before': + suites[0].beforeAll(fn); + break; + case 'after': + suites[0].afterAll(fn); + break; + case 'beforeEach': + suites[0].beforeEach(fn); + break; + case 'afterEach': + suites[0].afterEach(fn); + break; + default: + suites[0].addTest(new Test(key, fn)); + } + } else { + var suite = Suite.create(suites[0], key); + suites.unshift(suite); + visit(obj[key]); + suites.shift(); + } + } + } +}; + +}); // module: interfaces/exports.js + +require.register("interfaces/index.js", function(module, exports, require){ + +exports.bdd = require('./bdd'); +exports.tdd = require('./tdd'); +exports.qunit = require('./qunit'); +exports.exports = require('./exports'); + +}); // module: interfaces/index.js + +require.register("interfaces/qunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * QUnit-style interface: + * + * suite('Array'); + * + * test('#length', function(){ + * var arr = [1,2,3]; + * ok(arr.length == 3); + * }); + * + * test('#indexOf()', function(){ + * var arr = [1,2,3]; + * ok(arr.indexOf(1) == 0); + * ok(arr.indexOf(2) == 1); + * ok(arr.indexOf(3) == 2); + * }); + * + * suite('String'); + * + * test('#length', function(){ + * ok('foo'.length == 3); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before running tests. + */ + + context.before = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after running tests. + */ + + context.after = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Execute before each test case. + */ + + context.beforeEach = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.afterEach = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Describe a "suite" with the given `title`. + */ + + context.suite = function(title){ + if (suites.length > 1) suites.shift(); + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + return suite; + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var test = new Test(title, fn); + suites[0].addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/qunit.js + +require.register("interfaces/tdd.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Suite = require('../suite') + , Test = require('../test'); + +/** + * TDD-style interface: + * + * suite('Array', function(){ + * suite('#indexOf()', function(){ + * suiteSetup(function(){ + * + * }); + * + * test('should return -1 when not present', function(){ + * + * }); + * + * test('should return the index when present', function(){ + * + * }); + * + * suiteTeardown(function(){ + * + * }); + * }); + * }); + * + */ + +module.exports = function(suite){ + var suites = [suite]; + + suite.on('pre-require', function(context, file, mocha){ + + /** + * Execute before each test case. + */ + + context.setup = function(fn){ + suites[0].beforeEach(fn); + }; + + /** + * Execute after each test case. + */ + + context.teardown = function(fn){ + suites[0].afterEach(fn); + }; + + /** + * Execute before the suite. + */ + + context.suiteSetup = function(fn){ + suites[0].beforeAll(fn); + }; + + /** + * Execute after the suite. + */ + + context.suiteTeardown = function(fn){ + suites[0].afterAll(fn); + }; + + /** + * Describe a "suite" with the given `title` + * and callback `fn` containing nested suites + * and/or tests. + */ + + context.suite = function(title, fn){ + var suite = Suite.create(suites[0], title); + suites.unshift(suite); + fn.call(suite); + suites.shift(); + return suite; + }; + + /** + * Pending suite. + */ + context.suite.skip = function(title, fn) { + var suite = Suite.create(suites[0], title); + suite.pending = true; + suites.unshift(suite); + fn.call(suite); + suites.shift(); + }; + + /** + * Exclusive test-case. + */ + + context.suite.only = function(title, fn){ + var suite = context.suite(title, fn); + mocha.grep(suite.fullTitle()); + }; + + /** + * Describe a specification or test-case + * with the given `title` and callback `fn` + * acting as a thunk. + */ + + context.test = function(title, fn){ + var suite = suites[0]; + if (suite.pending) var fn = null; + var test = new Test(title, fn); + suite.addTest(test); + return test; + }; + + /** + * Exclusive test-case. + */ + + context.test.only = function(title, fn){ + var test = context.test(title, fn); + mocha.grep(test.fullTitle()); + }; + + /** + * Pending test case. + */ + + context.test.skip = function(title){ + context.test(title); + }; + }); +}; + +}); // module: interfaces/tdd.js + +require.register("mocha.js", function(module, exports, require){ +/*! + * mocha + * Copyright(c) 2011 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var path = require('browser/path') + , utils = require('./utils'); + +/** + * Expose `Mocha`. + */ + +exports = module.exports = Mocha; + +/** + * Expose internals. + */ + +exports.utils = utils; +exports.interfaces = require('./interfaces'); +exports.reporters = require('./reporters'); +exports.Runnable = require('./runnable'); +exports.Context = require('./context'); +exports.Runner = require('./runner'); +exports.Suite = require('./suite'); +exports.Hook = require('./hook'); +exports.Test = require('./test'); + +/** + * Return image `name` path. + * + * @param {String} name + * @return {String} + * @api private + */ + +function image(name) { + return __dirname + '/../images/' + name + '.png'; +} + +/** + * Setup mocha with `options`. + * + * Options: + * + * - `ui` name "bdd", "tdd", "exports" etc + * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` + * - `globals` array of accepted globals + * - `timeout` timeout in milliseconds + * - `bail` bail on the first test failure + * - `slow` milliseconds to wait before considering a test slow + * - `ignoreLeaks` ignore global leaks + * - `grep` string or regexp to filter tests with + * + * @param {Object} options + * @api public + */ + +function Mocha(options) { + options = options || {}; + this.files = []; + this.options = options; + this.grep(options.grep); + this.suite = new exports.Suite('', new exports.Context); + this.ui(options.ui); + this.bail(options.bail); + this.reporter(options.reporter); + if (options.timeout) this.timeout(options.timeout); + if (options.slow) this.slow(options.slow); +} + +/** + * Enable or disable bailing on the first failure. + * + * @param {Boolean} [bail] + * @api public + */ + +Mocha.prototype.bail = function(bail){ + if (0 == arguments.length) bail = true; + this.suite.bail(bail); + return this; +}; + +/** + * Add test `file`. + * + * @param {String} file + * @api public + */ + +Mocha.prototype.addFile = function(file){ + this.files.push(file); + return this; +}; + +/** + * Set reporter to `reporter`, defaults to "dot". + * + * @param {String|Function} reporter name or constructor + * @api public + */ + +Mocha.prototype.reporter = function(reporter){ + if ('function' == typeof reporter) { + this._reporter = reporter; + } else { + reporter = reporter || 'dot'; + try { + this._reporter = require('./reporters/' + reporter); + } catch (err) { + this._reporter = require(reporter); + } + if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); + } + return this; +}; + +/** + * Set test UI `name`, defaults to "bdd". + * + * @param {String} bdd + * @api public + */ + +Mocha.prototype.ui = function(name){ + name = name || 'bdd'; + this._ui = exports.interfaces[name]; + if (!this._ui) throw new Error('invalid interface "' + name + '"'); + this._ui = this._ui(this.suite); + return this; +}; + +/** + * Load registered files. + * + * @api private + */ + +Mocha.prototype.loadFiles = function(fn){ + var self = this; + var suite = this.suite; + var pending = this.files.length; + this.files.forEach(function(file){ + file = path.resolve(file); + suite.emit('pre-require', global, file, self); + suite.emit('require', require(file), file, self); + suite.emit('post-require', global, file, self); + --pending || (fn && fn()); + }); +}; + +/** + * Enable growl support. + * + * @api private + */ + +Mocha.prototype._growl = function(runner, reporter) { + var notify = require('growl'); + + runner.on('end', function(){ + var stats = reporter.stats; + if (stats.failures) { + var msg = stats.failures + ' of ' + runner.total + ' tests failed'; + notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); + } else { + notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { + name: 'mocha' + , title: 'Passed' + , image: image('ok') + }); + } + }); +}; + +/** + * Add regexp to grep, if `re` is a string it is escaped. + * + * @param {RegExp|String} re + * @return {Mocha} + * @api public + */ + +Mocha.prototype.grep = function(re){ + this.options.grep = 'string' == typeof re + ? new RegExp(utils.escapeRegexp(re)) + : re; + return this; +}; + +/** + * Invert `.grep()` matches. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.invert = function(){ + this.options.invert = true; + return this; +}; + +/** + * Ignore global leaks. + * + * @param {Boolean} ignore + * @return {Mocha} + * @api public + */ + +Mocha.prototype.ignoreLeaks = function(ignore){ + this.options.ignoreLeaks = !!ignore; + return this; +}; + +/** + * Enable global leak checking. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.checkLeaks = function(){ + this.options.ignoreLeaks = false; + return this; +}; + +/** + * Enable growl support. + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.growl = function(){ + this.options.growl = true; + return this; +}; + +/** + * Ignore `globals` array or string. + * + * @param {Array|String} globals + * @return {Mocha} + * @api public + */ + +Mocha.prototype.globals = function(globals){ + this.options.globals = (this.options.globals || []).concat(globals); + return this; +}; + +/** + * Set the timeout in milliseconds. + * + * @param {Number} timeout + * @return {Mocha} + * @api public + */ + +Mocha.prototype.timeout = function(timeout){ + this.suite.timeout(timeout); + return this; +}; + +/** + * Set slowness threshold in milliseconds. + * + * @param {Number} slow + * @return {Mocha} + * @api public + */ + +Mocha.prototype.slow = function(slow){ + this.suite.slow(slow); + return this; +}; + +/** + * Makes all tests async (accepting a callback) + * + * @return {Mocha} + * @api public + */ + +Mocha.prototype.asyncOnly = function(){ + this.options.asyncOnly = true; + return this; +}; + +/** + * Run tests and invoke `fn()` when complete. + * + * @param {Function} fn + * @return {Runner} + * @api public + */ + +Mocha.prototype.run = function(fn){ + if (this.files.length) this.loadFiles(); + var suite = this.suite; + var options = this.options; + var runner = new exports.Runner(suite); + var reporter = new this._reporter(runner); + runner.ignoreLeaks = false !== options.ignoreLeaks; + runner.asyncOnly = options.asyncOnly; + if (options.grep) runner.grep(options.grep, options.invert); + if (options.globals) runner.globals(options.globals); + if (options.growl) this._growl(runner, reporter); + return runner.run(fn); +}; + +}); // module: mocha.js + +require.register("ms.js", function(module, exports, require){ + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; + +/** + * Parse or format the given `val`. + * + * @param {String|Number} val + * @return {String|Number} + * @api public + */ + +module.exports = function(val){ + if ('string' == typeof val) return parse(val); + return format(val); +} + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); + if (!m) return; + var n = parseFloat(m[1]); + var type = (m[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'y': + return n * 31557600000; + case 'days': + case 'day': + case 'd': + return n * 86400000; + case 'hours': + case 'hour': + case 'h': + return n * 3600000; + case 'minutes': + case 'minute': + case 'm': + return n * 60000; + case 'seconds': + case 'second': + case 's': + return n * 1000; + case 'ms': + return n; + } +} + +/** + * Format the given `ms`. + * + * @param {Number} ms + * @return {String} + * @api public + */ + +function format(ms) { + if (ms == d) return Math.round(ms / d) + ' day'; + if (ms > d) return Math.round(ms / d) + ' days'; + if (ms == h) return Math.round(ms / h) + ' hour'; + if (ms > h) return Math.round(ms / h) + ' hours'; + if (ms == m) return Math.round(ms / m) + ' minute'; + if (ms > m) return Math.round(ms / m) + ' minutes'; + if (ms == s) return Math.round(ms / s) + ' second'; + if (ms > s) return Math.round(ms / s) + ' seconds'; + return ms + ' ms'; +} +}); // module: ms.js + +require.register("reporters/base.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var tty = require('browser/tty') + , diff = require('browser/diff') + , ms = require('../ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Check if both stdio streams are associated with a tty. + */ + +var isatty = tty.isatty(1) && tty.isatty(2); + +/** + * Expose `Base`. + */ + +exports = module.exports = Base; + +/** + * Enable coloring by default. + */ + +exports.useColors = isatty; + +/** + * Default color map. + */ + +exports.colors = { + 'pass': 90 + , 'fail': 31 + , 'bright pass': 92 + , 'bright fail': 91 + , 'bright yellow': 93 + , 'pending': 36 + , 'suite': 0 + , 'error title': 0 + , 'error message': 31 + , 'error stack': 90 + , 'checkmark': 32 + , 'fast': 90 + , 'medium': 33 + , 'slow': 31 + , 'green': 32 + , 'light': 90 + , 'diff gutter': 90 + , 'diff added': 42 + , 'diff removed': 41 +}; + +/** + * Default symbol map. + */ + +exports.symbols = { + ok: '✓', + err: '✖', + dot: '․' +}; + +// With node.js on Windows: use symbols available in terminal default fonts +if ('win32' == process.platform) { + exports.symbols.ok = '\u221A'; + exports.symbols.err = '\u00D7'; + exports.symbols.dot = '.'; +} + +/** + * Color `str` with the given `type`, + * allowing colors to be disabled, + * as well as user-defined color + * schemes. + * + * @param {String} type + * @param {String} str + * @return {String} + * @api private + */ + +var color = exports.color = function(type, str) { + if (!exports.useColors) return str; + return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; +}; + +/** + * Expose term window size, with some + * defaults for when stderr is not a tty. + */ + +exports.window = { + width: isatty + ? process.stdout.getWindowSize + ? process.stdout.getWindowSize(1)[0] + : tty.getWindowSize()[1] + : 75 +}; + +/** + * Expose some basic cursor interactions + * that are common among reporters. + */ + +exports.cursor = { + hide: function(){ + process.stdout.write('\u001b[?25l'); + }, + + show: function(){ + process.stdout.write('\u001b[?25h'); + }, + + deleteLine: function(){ + process.stdout.write('\u001b[2K'); + }, + + beginningOfLine: function(){ + process.stdout.write('\u001b[0G'); + }, + + CR: function(){ + exports.cursor.deleteLine(); + exports.cursor.beginningOfLine(); + } +}; + +/** + * Outut the given `failures` as a list. + * + * @param {Array} failures + * @api public + */ + +exports.list = function(failures){ + console.error(); + failures.forEach(function(test, i){ + // format + var fmt = color('error title', ' %s) %s:\n') + + color('error message', ' %s') + + color('error stack', '\n%s\n'); + + // msg + var err = test.err + , message = err.message || '' + , stack = err.stack || message + , index = stack.indexOf(message) + message.length + , msg = stack.slice(0, index) + , actual = err.actual + , expected = err.expected + , escape = true; + + // uncaught + if (err.uncaught) { + msg = 'Uncaught ' + msg; + } + + // explicitly show diff + if (err.showDiff && sameType(actual, expected)) { + escape = false; + err.actual = actual = stringify(actual); + err.expected = expected = stringify(expected); + } + + // actual / expected diff + if ('string' == typeof actual && 'string' == typeof expected) { + msg = errorDiff(err, 'Words', escape); + + // linenos + var lines = msg.split('\n'); + if (lines.length > 4) { + var width = String(lines.length).length; + msg = lines.map(function(str, i){ + return pad(++i, width) + ' |' + ' ' + str; + }).join('\n'); + } + + // legend + msg = '\n' + + color('diff removed', 'actual') + + ' ' + + color('diff added', 'expected') + + '\n\n' + + msg + + '\n'; + + // indent + msg = msg.replace(/^/gm, ' '); + + fmt = color('error title', ' %s) %s:\n%s') + + color('error stack', '\n%s\n'); + } + + // indent stack trace without msg + stack = stack.slice(index ? index + 1 : index) + .replace(/^/gm, ' '); + + console.error(fmt, (i + 1), test.fullTitle(), msg, stack); + }); +}; + +/** + * Initialize a new `Base` reporter. + * + * All other reporters generally + * inherit from this reporter, providing + * stats such as test duration, number + * of tests passed / failed etc. + * + * @param {Runner} runner + * @api public + */ + +function Base(runner) { + var self = this + , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } + , failures = this.failures = []; + + if (!runner) return; + this.runner = runner; + + runner.stats = stats; + + runner.on('start', function(){ + stats.start = new Date; + }); + + runner.on('suite', function(suite){ + stats.suites = stats.suites || 0; + suite.root || stats.suites++; + }); + + runner.on('test end', function(test){ + stats.tests = stats.tests || 0; + stats.tests++; + }); + + runner.on('pass', function(test){ + stats.passes = stats.passes || 0; + + var medium = test.slow() / 2; + test.speed = test.duration > test.slow() + ? 'slow' + : test.duration > medium + ? 'medium' + : 'fast'; + + stats.passes++; + }); + + runner.on('fail', function(test, err){ + stats.failures = stats.failures || 0; + stats.failures++; + test.err = err; + failures.push(test); + }); + + runner.on('end', function(){ + stats.end = new Date; + stats.duration = new Date - stats.start; + }); + + runner.on('pending', function(){ + stats.pending++; + }); +} + +/** + * Output common epilogue used by many of + * the bundled reporters. + * + * @api public + */ + +Base.prototype.epilogue = function(){ + var stats = this.stats; + var tests; + var fmt; + + console.log(); + + // passes + fmt = color('bright pass', ' ') + + color('green', ' %d passing') + + color('light', ' (%s)'); + + console.log(fmt, + stats.passes || 0, + ms(stats.duration)); + + // pending + if (stats.pending) { + fmt = color('pending', ' ') + + color('pending', ' %d pending'); + + console.log(fmt, stats.pending); + } + + // failures + if (stats.failures) { + fmt = color('fail', ' %d failing'); + + console.error(fmt, + stats.failures); + + Base.list(this.failures); + console.error(); + } + + console.log(); +}; + +/** + * Pad the given `str` to `len`. + * + * @param {String} str + * @param {String} len + * @return {String} + * @api private + */ + +function pad(str, len) { + str = String(str); + return Array(len - str.length + 1).join(' ') + str; +} + +/** + * Return a character diff for `err`. + * + * @param {Error} err + * @return {String} + * @api private + */ + +function errorDiff(err, type, escape) { + return diff['diff' + type](err.actual, err.expected).map(function(str){ + if (escape) { + str.value = str.value + .replace(/\t/g, '') + .replace(/\r/g, '') + .replace(/\n/g, '\n'); + } + if (str.added) return colorLines('diff added', str.value); + if (str.removed) return colorLines('diff removed', str.value); + return str.value; + }).join(''); +} + +/** + * Color lines for `str`, using the color `name`. + * + * @param {String} name + * @param {String} str + * @return {String} + * @api private + */ + +function colorLines(name, str) { + return str.split('\n').map(function(str){ + return color(name, str); + }).join('\n'); +} + +/** + * Stringify `obj`. + * + * @param {Mixed} obj + * @return {String} + * @api private + */ + +function stringify(obj) { + if (obj instanceof RegExp) return obj.toString(); + return JSON.stringify(obj, null, 2); +} + +/** + * Check that a / b have the same type. + * + * @param {Object} a + * @param {Object} b + * @return {Boolean} + * @api private + */ + +function sameType(a, b) { + a = Object.prototype.toString.call(a); + b = Object.prototype.toString.call(b); + return a == b; +} + +}); // module: reporters/base.js + +require.register("reporters/doc.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Doc`. + */ + +exports = module.exports = Doc; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Doc(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , indents = 2; + + function indent() { + return Array(indents).join(' '); + } + + runner.on('suite', function(suite){ + if (suite.root) return; + ++indents; + console.log('%s
', indent()); + ++indents; + console.log('%s

%s

', indent(), utils.escape(suite.title)); + console.log('%s
', indent()); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + console.log('%s
', indent()); + --indents; + console.log('%s
', indent()); + --indents; + }); + + runner.on('pass', function(test){ + console.log('%s
%s
', indent(), utils.escape(test.title)); + var code = utils.escape(utils.clean(test.fn.toString())); + console.log('%s
%s
', indent(), code); + }); +} + +}); // module: reporters/doc.js + +require.register("reporters/dot.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = Dot; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Dot(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , n = 0; + + runner.on('start', function(){ + process.stdout.write('\n '); + }); + + runner.on('pending', function(test){ + process.stdout.write(color('pending', Base.symbols.dot)); + }); + + runner.on('pass', function(test){ + if (++n % width == 0) process.stdout.write('\n '); + if ('slow' == test.speed) { + process.stdout.write(color('bright yellow', Base.symbols.dot)); + } else { + process.stdout.write(color(test.speed, Base.symbols.dot)); + } + }); + + runner.on('fail', function(test, err){ + if (++n % width == 0) process.stdout.write('\n '); + process.stdout.write(color('fail', Base.symbols.dot)); + }); + + runner.on('end', function(){ + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Dot.prototype = new F; +Dot.prototype.constructor = Dot; + +}); // module: reporters/dot.js + +require.register("reporters/html-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var JSONCov = require('./json-cov') + , fs = require('browser/fs'); + +/** + * Expose `HTMLCov`. + */ + +exports = module.exports = HTMLCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTMLCov(runner) { + var jade = require('jade') + , file = __dirname + '/templates/coverage.jade' + , str = fs.readFileSync(file, 'utf8') + , fn = jade.compile(str, { filename: file }) + , self = this; + + JSONCov.call(this, runner, false); + + runner.on('end', function(){ + process.stdout.write(fn({ + cov: self.cov + , coverageClass: coverageClass + })); + }); +} + +/** + * Return coverage class for `n`. + * + * @return {String} + * @api private + */ + +function coverageClass(n) { + if (n >= 75) return 'high'; + if (n >= 50) return 'medium'; + if (n >= 25) return 'low'; + return 'terrible'; +} +}); // module: reporters/html-cov.js + +require.register("reporters/html.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , Progress = require('../browser/progress') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `Doc`. + */ + +exports = module.exports = HTML; + + +/** + * Stats template. + */ + +var statsTemplate = ''; + +/** + * Initialize a new `Doc` reporter. + * + * @param {Runner} runner + * @api public + */ + +function HTML(runner, root) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total + , stat = fragment(statsTemplate) + , items = stat.getElementsByTagName('li') + , passes = items[1].getElementsByTagName('em')[0] + , passesLink = items[1].getElementsByTagName('a')[0] + , pending = items[2].getElementsByTagName('em')[0] + , pendingLink = items[2].getElementsByTagName('a')[0] + , failures = items[3].getElementsByTagName('em')[0] + , failuresLink = items[3].getElementsByTagName('a')[0] + , duration = items[4].getElementsByTagName('em')[0] + , canvas = stat.getElementsByTagName('canvas')[0] + , report = fragment('
    ') + , stack = [report] + , progress + , ctx + + root = root || document.getElementById('mocha'); + + if (canvas.getContext) { + var ratio = window.devicePixelRatio || 1; + canvas.style.width = canvas.width; + canvas.style.height = canvas.height; + canvas.width *= ratio; + canvas.height *= ratio; + ctx = canvas.getContext('2d'); + ctx.scale(ratio, ratio); + progress = new Progress; + } + + if (!root) return error('#mocha div missing, add it to your document'); + + // pass toggle + on(passesLink, 'click', function(){ + unhide(); + var name = /pass/.test(report.className) ? '' : ' pass'; + report.className = report.className.replace(/fail|pass|pending/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test pass'); + }); + + // pending toggle + on(pendingLink, 'click', function(){ + unhide(); + var name = /pending/.test(report.className) ? '' : ' pending'; + report.className = report.className.replace(/fail|pass|pending/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test pending'); + }); + + // failure toggle + on(failuresLink, 'click', function(){ + unhide(); + var name = /fail/.test(report.className) ? '' : ' fail'; + report.className = report.className.replace(/fail|pass|pending/g, '') + name; + if (report.className.trim()) hideSuitesWithout('test fail'); + }); + + root.appendChild(stat); + root.appendChild(report); + + if (progress) progress.size(40); + + runner.on('suite', function(suite){ + if (suite.root) return; + + // suite + var url = '?grep=' + encodeURIComponent(suite.fullTitle()); + var el = fragment('
  • %s

  • ', url, escape(suite.title)); + + // container + stack[0].appendChild(el); + stack.unshift(document.createElement('ul')); + el.appendChild(stack[0]); + }); + + runner.on('suite end', function(suite){ + if (suite.root) return; + stack.shift(); + }); + + runner.on('fail', function(test, err){ + if ('hook' == test.type) runner.emit('test end', test); + }); + + runner.on('test end', function(test){ + // TODO: add to stats + var percent = stats.tests / this.total * 100 | 0; + if (progress) progress.update(percent).draw(ctx); + + // update stats + var ms = new Date - stats.start; + text(passes, stats.passes); + text(failures, stats.failures); + text(pending, stats.pending); + text(duration, (ms / 1000).toFixed(2)); + + // test + if ('passed' == test.state) { + var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); + } else if (test.pending) { + var el = fragment('
  • %e

  • ', test.title); + } else { + var el = fragment('
  • %e

  • ', test.title, encodeURIComponent(test.fullTitle())); + var str = test.err.stack || test.err.toString(); + + // FF / Opera do not add the message + if (!~str.indexOf(test.err.message)) { + str = test.err.message + '\n' + str; + } + + // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we + // check for the result of the stringifying. + if ('[object Error]' == str) str = test.err.message; + + // Safari doesn't give you a stack. Let's at least provide a source line. + if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { + str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; + } + + el.appendChild(fragment('
    %e
    ', str)); + } + + // toggle code + // TODO: defer + if (!test.pending) { + var h2 = el.getElementsByTagName('h2')[0]; + + on(h2, 'click', function(){ + pre.style.display = 'none' == pre.style.display + ? 'block' + : 'none'; + }); + + var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); + el.appendChild(pre); + pre.style.display = 'none'; + } + + // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. + if (stack[0]) stack[0].appendChild(el); + }); +} + +/** + * Display error `msg`. + */ + +function error(msg) { + document.body.appendChild(fragment('
    %s
    ', msg)); +} + +/** + * Return a DOM fragment from `html`. + */ + +function fragment(html) { + var args = arguments + , div = document.createElement('div') + , i = 1; + + div.innerHTML = html.replace(/%([se])/g, function(_, type){ + switch (type) { + case 's': return String(args[i++]); + case 'e': return escape(args[i++]); + } + }); + + return div.firstChild; +} + +/** + * Check for suites that do not have elements + * with `classname`, and hide them. + */ + +function hideSuitesWithout(classname) { + var suites = document.getElementsByClassName('suite'); + for (var i = 0; i < suites.length; i++) { + var els = suites[i].getElementsByClassName(classname); + if (0 == els.length && !/hidden/.test(suites[i].className)) suites[i].className += ' hidden'; + } +} + +/** + * Unhide .hidden suites. + */ + +function unhide() { + var els = document.getElementsByClassName('suite'); + for (var i = 0; i < els.length; ++i) { + els[i].className = els[i].className.replace('suite hidden', 'suite'); + } +} + +/** + * Set `el` text to `str`. + */ + +function text(el, str) { + if (el.textContent) { + el.textContent = str; + } else { + el.innerText = str; + } +} + +/** + * Listen on `event` with callback `fn`. + */ + +function on(el, event, fn) { + if (el.addEventListener) { + el.addEventListener(event, fn, false); + } else { + el.attachEvent('on' + event, fn); + } +} + +}); // module: reporters/html.js + +require.register("reporters/index.js", function(module, exports, require){ + +exports.Base = require('./base'); +exports.Dot = require('./dot'); +exports.Doc = require('./doc'); +exports.TAP = require('./tap'); +exports.JSON = require('./json'); +exports.HTML = require('./html'); +exports.List = require('./list'); +exports.Min = require('./min'); +exports.Spec = require('./spec'); +exports.Nyan = require('./nyan'); +exports.XUnit = require('./xunit'); +exports.Markdown = require('./markdown'); +exports.Progress = require('./progress'); +exports.Landing = require('./landing'); +exports.JSONCov = require('./json-cov'); +exports.HTMLCov = require('./html-cov'); +exports.JSONStream = require('./json-stream'); +exports.Teamcity = require('./teamcity'); + +}); // module: reporters/index.js + +require.register("reporters/json-cov.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `JSONCov`. + */ + +exports = module.exports = JSONCov; + +/** + * Initialize a new `JsCoverage` reporter. + * + * @param {Runner} runner + * @param {Boolean} output + * @api public + */ + +function JSONCov(runner, output) { + var self = this + , output = 1 == arguments.length ? true : output; + + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var cov = global._$jscoverage || {}; + var result = self.cov = map(cov); + result.stats = self.stats; + result.tests = tests.map(clean); + result.failures = failures.map(clean); + result.passes = passes.map(clean); + if (!output) return; + process.stdout.write(JSON.stringify(result, null, 2 )); + }); +} + +/** + * Map jscoverage data to a JSON structure + * suitable for reporting. + * + * @param {Object} cov + * @return {Object} + * @api private + */ + +function map(cov) { + var ret = { + instrumentation: 'node-jscoverage' + , sloc: 0 + , hits: 0 + , misses: 0 + , coverage: 0 + , files: [] + }; + + for (var filename in cov) { + var data = coverage(filename, cov[filename]); + ret.files.push(data); + ret.hits += data.hits; + ret.misses += data.misses; + ret.sloc += data.sloc; + } + + ret.files.sort(function(a, b) { + return a.filename.localeCompare(b.filename); + }); + + if (ret.sloc > 0) { + ret.coverage = (ret.hits / ret.sloc) * 100; + } + + return ret; +}; + +/** + * Map jscoverage data for a single source file + * to a JSON structure suitable for reporting. + * + * @param {String} filename name of the source file + * @param {Object} data jscoverage coverage data + * @return {Object} + * @api private + */ + +function coverage(filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc: 0, + source: {} + }; + + data.source.forEach(function(line, num){ + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + ret.source[num] = { + source: line + , coverage: data[num] === undefined + ? '' + : data[num] + }; + }); + + ret.coverage = ret.hits / ret.sloc * 100; + + return ret; +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} + +}); // module: reporters/json-cov.js + +require.register("reporters/json-stream.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , total = runner.total; + + runner.on('start', function(){ + console.log(JSON.stringify(['start', { total: total }])); + }); + + runner.on('pass', function(test){ + console.log(JSON.stringify(['pass', clean(test)])); + }); + + runner.on('fail', function(test, err){ + console.log(JSON.stringify(['fail', clean(test)])); + }); + + runner.on('end', function(){ + process.stdout.write(JSON.stringify(['end', self.stats])); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json-stream.js + +require.register("reporters/json.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `JSON`. + */ + +exports = module.exports = JSONReporter; + +/** + * Initialize a new `JSON` reporter. + * + * @param {Runner} runner + * @api public + */ + +function JSONReporter(runner) { + var self = this; + Base.call(this, runner); + + var tests = [] + , failures = [] + , passes = []; + + runner.on('test end', function(test){ + tests.push(test); + }); + + runner.on('pass', function(test){ + passes.push(test); + }); + + runner.on('fail', function(test){ + failures.push(test); + }); + + runner.on('end', function(){ + var obj = { + stats: self.stats + , tests: tests.map(clean) + , failures: failures.map(clean) + , passes: passes.map(clean) + }; + + process.stdout.write(JSON.stringify(obj, null, 2)); + }); +} + +/** + * Return a plain-object representation of `test` + * free of cyclic properties etc. + * + * @param {Object} test + * @return {Object} + * @api private + */ + +function clean(test) { + return { + title: test.title + , fullTitle: test.fullTitle() + , duration: test.duration + } +} +}); // module: reporters/json.js + +require.register("reporters/landing.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Landing`. + */ + +exports = module.exports = Landing; + +/** + * Airplane color. + */ + +Base.colors.plane = 0; + +/** + * Airplane crash color. + */ + +Base.colors['plane crash'] = 31; + +/** + * Runway color. + */ + +Base.colors.runway = 90; + +/** + * Initialize a new `Landing` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Landing(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , total = runner.total + , stream = process.stdout + , plane = color('plane', '✈') + , crashed = -1 + , n = 0; + + function runway() { + var buf = Array(width).join('-'); + return ' ' + color('runway', buf); + } + + runner.on('start', function(){ + stream.write('\n '); + cursor.hide(); + }); + + runner.on('test end', function(test){ + // check if the plane crashed + var col = -1 == crashed + ? width * ++n / total | 0 + : crashed; + + // show the crash + if ('failed' == test.state) { + plane = color('plane crash', '✈'); + crashed = col; + } + + // render landing strip + stream.write('\u001b[4F\n\n'); + stream.write(runway()); + stream.write('\n '); + stream.write(color('runway', Array(col).join('⋅'))); + stream.write(plane) + stream.write(color('runway', Array(width - col).join('⋅') + '\n')); + stream.write(runway()); + stream.write('\u001b[0m'); + }); + + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Landing.prototype = new F; +Landing.prototype.constructor = Landing; + +}); // module: reporters/landing.js + +require.register("reporters/list.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `List`. + */ + +exports = module.exports = List; + +/** + * Initialize a new `List` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function List(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 0; + + runner.on('start', function(){ + console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = color('checkmark', ' -') + + color('pending', ' %s'); + console.log(fmt, test.fullTitle()); + }); + + runner.on('pass', function(test){ + var fmt = color('checkmark', ' '+Base.symbols.dot) + + color('pass', ' %s: ') + + color(test.speed, '%dms'); + cursor.CR(); + console.log(fmt, test.fullTitle(), test.duration); + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +List.prototype = new F; +List.prototype.constructor = List; + + +}); // module: reporters/list.js + +require.register("reporters/markdown.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils'); + +/** + * Expose `Markdown`. + */ + +exports = module.exports = Markdown; + +/** + * Initialize a new `Markdown` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Markdown(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , level = 0 + , buf = ''; + + function title(str) { + return Array(level).join('#') + ' ' + str; + } + + function indent() { + return Array(level).join(' '); + } + + function mapTOC(suite, obj) { + var ret = obj; + obj = obj[suite.title] = obj[suite.title] || { suite: suite }; + suite.suites.forEach(function(suite){ + mapTOC(suite, obj); + }); + return ret; + } + + function stringifyTOC(obj, level) { + ++level; + var buf = ''; + var link; + for (var key in obj) { + if ('suite' == key) continue; + if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; + if (key) buf += Array(level).join(' ') + link; + buf += stringifyTOC(obj[key], level); + } + --level; + return buf; + } + + function generateTOC(suite) { + var obj = mapTOC(suite, {}); + return stringifyTOC(obj, 0); + } + + generateTOC(runner.suite); + + runner.on('suite', function(suite){ + ++level; + var slug = utils.slug(suite.fullTitle()); + buf += '' + '\n'; + buf += title(suite.title) + '\n'; + }); + + runner.on('suite end', function(suite){ + --level; + }); + + runner.on('pass', function(test){ + var code = utils.clean(test.fn.toString()); + buf += test.title + '.\n'; + buf += '\n```js\n'; + buf += code + '\n'; + buf += '```\n\n'; + }); + + runner.on('end', function(){ + process.stdout.write('# TOC\n'); + process.stdout.write(generateTOC(runner.suite)); + process.stdout.write(buf); + }); +} +}); // module: reporters/markdown.js + +require.register("reporters/min.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Min`. + */ + +exports = module.exports = Min; + +/** + * Initialize a new `Min` minimal test reporter (best used with --watch). + * + * @param {Runner} runner + * @api public + */ + +function Min(runner) { + Base.call(this, runner); + + runner.on('start', function(){ + // clear screen + process.stdout.write('\u001b[2J'); + // set cursor position + process.stdout.write('\u001b[1;3H'); + }); + + runner.on('end', this.epilogue.bind(this)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Min.prototype = new F; +Min.prototype.constructor = Min; + + +}); // module: reporters/min.js + +require.register("reporters/nyan.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var Base = require('./base') + , color = Base.color; + +/** + * Expose `Dot`. + */ + +exports = module.exports = NyanCat; + +/** + * Initialize a new `Dot` matrix test reporter. + * + * @param {Runner} runner + * @api public + */ + +function NyanCat(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , width = Base.window.width * .75 | 0 + , rainbowColors = this.rainbowColors = self.generateColors() + , colorIndex = this.colorIndex = 0 + , numerOfLines = this.numberOfLines = 4 + , trajectories = this.trajectories = [[], [], [], []] + , nyanCatWidth = this.nyanCatWidth = 11 + , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) + , scoreboardWidth = this.scoreboardWidth = 5 + , tick = this.tick = 0 + , n = 0; + + runner.on('start', function(){ + Base.cursor.hide(); + self.draw('start'); + }); + + runner.on('pending', function(test){ + self.draw('pending'); + }); + + runner.on('pass', function(test){ + self.draw('pass'); + }); + + runner.on('fail', function(test, err){ + self.draw('fail'); + }); + + runner.on('end', function(){ + Base.cursor.show(); + for (var i = 0; i < self.numberOfLines; i++) write('\n'); + self.epilogue(); + }); +} + +/** + * Draw the nyan cat with runner `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.draw = function(status){ + this.appendRainbow(); + this.drawScoreboard(); + this.drawRainbow(); + this.drawNyanCat(status); + this.tick = !this.tick; +}; + +/** + * Draw the "scoreboard" showing the number + * of passes, failures and pending tests. + * + * @api private + */ + +NyanCat.prototype.drawScoreboard = function(){ + var stats = this.stats; + var colors = Base.colors; + + function draw(color, n) { + write(' '); + write('\u001b[' + color + 'm' + n + '\u001b[0m'); + write('\n'); + } + + draw(colors.green, stats.passes); + draw(colors.fail, stats.failures); + draw(colors.pending, stats.pending); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Append the rainbow. + * + * @api private + */ + +NyanCat.prototype.appendRainbow = function(){ + var segment = this.tick ? '_' : '-'; + var rainbowified = this.rainbowify(segment); + + for (var index = 0; index < this.numberOfLines; index++) { + var trajectory = this.trajectories[index]; + if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); + trajectory.push(rainbowified); + } +}; + +/** + * Draw the rainbow. + * + * @api private + */ + +NyanCat.prototype.drawRainbow = function(){ + var self = this; + + this.trajectories.forEach(function(line, index) { + write('\u001b[' + self.scoreboardWidth + 'C'); + write(line.join('')); + write('\n'); + }); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Draw the nyan cat with `status`. + * + * @param {String} status + * @api private + */ + +NyanCat.prototype.drawNyanCat = function(status) { + var self = this; + var startWidth = this.scoreboardWidth + this.trajectories[0].length; + var color = '\u001b[' + startWidth + 'C'; + var padding = ''; + + write(color); + write('_,------,'); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write('_|' + padding + '/\\_/\\ '); + write('\n'); + + write(color); + padding = self.tick ? '_' : '__'; + var tail = self.tick ? '~' : '^'; + var face; + switch (status) { + case 'pass': + face = '( ^ .^)'; + break; + case 'fail': + face = '( o .o)'; + break; + default: + face = '( - .-)'; + } + write(tail + '|' + padding + face + ' '); + write('\n'); + + write(color); + padding = self.tick ? ' ' : ' '; + write(padding + '"" "" '); + write('\n'); + + this.cursorUp(this.numberOfLines); +}; + +/** + * Move cursor up `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorUp = function(n) { + write('\u001b[' + n + 'A'); +}; + +/** + * Move cursor down `n`. + * + * @param {Number} n + * @api private + */ + +NyanCat.prototype.cursorDown = function(n) { + write('\u001b[' + n + 'B'); +}; + +/** + * Generate rainbow colors. + * + * @return {Array} + * @api private + */ + +NyanCat.prototype.generateColors = function(){ + var colors = []; + + for (var i = 0; i < (6 * 7); i++) { + var pi3 = Math.floor(Math.PI / 3); + var n = (i * (1.0 / 6)); + var r = Math.floor(3 * Math.sin(n) + 3); + var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); + var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); + colors.push(36 * r + 6 * g + b + 16); + } + + return colors; +}; + +/** + * Apply rainbow to the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +NyanCat.prototype.rainbowify = function(str){ + var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; + this.colorIndex += 1; + return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; +}; + +/** + * Stdout helper. + */ + +function write(string) { + process.stdout.write(string); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +NyanCat.prototype = new F; +NyanCat.prototype.constructor = NyanCat; + + +}); // module: reporters/nyan.js + +require.register("reporters/progress.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Progress`. + */ + +exports = module.exports = Progress; + +/** + * General progress bar color. + */ + +Base.colors.progress = 90; + +/** + * Initialize a new `Progress` bar test reporter. + * + * @param {Runner} runner + * @param {Object} options + * @api public + */ + +function Progress(runner, options) { + Base.call(this, runner); + + var self = this + , options = options || {} + , stats = this.stats + , width = Base.window.width * .50 | 0 + , total = runner.total + , complete = 0 + , max = Math.max; + + // default chars + options.open = options.open || '['; + options.complete = options.complete || '▬'; + options.incomplete = options.incomplete || Base.symbols.dot; + options.close = options.close || ']'; + options.verbose = false; + + // tests started + runner.on('start', function(){ + console.log(); + cursor.hide(); + }); + + // tests complete + runner.on('test end', function(){ + complete++; + var incomplete = total - complete + , percent = complete / total + , n = width * percent | 0 + , i = width - n; + + cursor.CR(); + process.stdout.write('\u001b[J'); + process.stdout.write(color('progress', ' ' + options.open)); + process.stdout.write(Array(n).join(options.complete)); + process.stdout.write(Array(i).join(options.incomplete)); + process.stdout.write(color('progress', options.close)); + if (options.verbose) { + process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); + } + }); + + // tests are complete, output some stats + // and the failures if any + runner.on('end', function(){ + cursor.show(); + console.log(); + self.epilogue(); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Progress.prototype = new F; +Progress.prototype.constructor = Progress; + + +}); // module: reporters/progress.js + +require.register("reporters/spec.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `Spec`. + */ + +exports = module.exports = Spec; + +/** + * Initialize a new `Spec` test reporter. + * + * @param {Runner} runner + * @api public + */ + +function Spec(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , indents = 0 + , n = 0; + + function indent() { + return Array(indents).join(' ') + } + + runner.on('start', function(){ + console.log(); + }); + + runner.on('suite', function(suite){ + ++indents; + console.log(color('suite', '%s%s'), indent(), suite.title); + }); + + runner.on('suite end', function(suite){ + --indents; + if (1 == indents) console.log(); + }); + + runner.on('test', function(test){ + process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); + }); + + runner.on('pending', function(test){ + var fmt = indent() + color('pending', ' - %s'); + console.log(fmt, test.title); + }); + + runner.on('pass', function(test){ + if ('fast' == test.speed) { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s '); + cursor.CR(); + console.log(fmt, test.title); + } else { + var fmt = indent() + + color('checkmark', ' ' + Base.symbols.ok) + + color('pass', ' %s ') + + color(test.speed, '(%dms)'); + cursor.CR(); + console.log(fmt, test.title, test.duration); + } + }); + + runner.on('fail', function(test, err){ + cursor.CR(); + console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); + }); + + runner.on('end', self.epilogue.bind(self)); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +Spec.prototype = new F; +Spec.prototype.constructor = Spec; + + +}); // module: reporters/spec.js + +require.register("reporters/tap.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , cursor = Base.cursor + , color = Base.color; + +/** + * Expose `TAP`. + */ + +exports = module.exports = TAP; + +/** + * Initialize a new `TAP` reporter. + * + * @param {Runner} runner + * @api public + */ + +function TAP(runner) { + Base.call(this, runner); + + var self = this + , stats = this.stats + , n = 1 + , passes = 0 + , failures = 0; + + runner.on('start', function(){ + var total = runner.grepTotal(runner.suite); + console.log('%d..%d', 1, total); + }); + + runner.on('test end', function(){ + ++n; + }); + + runner.on('pending', function(test){ + console.log('ok %d %s # SKIP -', n, title(test)); + }); + + runner.on('pass', function(test){ + passes++; + console.log('ok %d %s', n, title(test)); + }); + + runner.on('fail', function(test, err){ + failures++; + console.log('not ok %d %s', n, title(test)); + if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); + }); + + runner.on('end', function(){ + console.log('# tests ' + (passes + failures)); + console.log('# pass ' + passes); + console.log('# fail ' + failures); + }); +} + +/** + * Return a TAP-safe title of `test` + * + * @param {Object} test + * @return {String} + * @api private + */ + +function title(test) { + return test.fullTitle().replace(/#/g, ''); +} + +}); // module: reporters/tap.js + +require.register("reporters/teamcity.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base'); + +/** + * Expose `Teamcity`. + */ + +exports = module.exports = Teamcity; + +/** + * Initialize a new `Teamcity` reporter. + * + * @param {Runner} runner + * @api public + */ + +function Teamcity(runner) { + Base.call(this, runner); + var stats = this.stats; + + runner.on('start', function() { + console.log("##teamcity[testSuiteStarted name='mocha.suite']"); + }); + + runner.on('test', function(test) { + console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); + }); + + runner.on('fail', function(test, err) { + console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); + }); + + runner.on('pending', function(test) { + console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); + }); + + runner.on('test end', function(test) { + console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); + }); + + runner.on('end', function() { + console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); + }); +} + +/** + * Escape the given `str`. + */ + +function escape(str) { + return str + .replace(/\|/g, "||") + .replace(/\n/g, "|n") + .replace(/\r/g, "|r") + .replace(/\[/g, "|[") + .replace(/\]/g, "|]") + .replace(/\u0085/g, "|x") + .replace(/\u2028/g, "|l") + .replace(/\u2029/g, "|p") + .replace(/'/g, "|'"); +} + +}); // module: reporters/teamcity.js + +require.register("reporters/xunit.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Base = require('./base') + , utils = require('../utils') + , escape = utils.escape; + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Expose `XUnit`. + */ + +exports = module.exports = XUnit; + +/** + * Initialize a new `XUnit` reporter. + * + * @param {Runner} runner + * @api public + */ + +function XUnit(runner) { + Base.call(this, runner); + var stats = this.stats + , tests = [] + , self = this; + + runner.on('pass', function(test){ + tests.push(test); + }); + + runner.on('fail', function(test){ + tests.push(test); + }); + + runner.on('end', function(){ + console.log(tag('testsuite', { + name: 'Mocha Tests' + , tests: stats.tests + , failures: stats.failures + , errors: stats.failures + , skipped: stats.tests - stats.failures - stats.passes + , timestamp: (new Date).toUTCString() + , time: (stats.duration / 1000) || 0 + }, false)); + + tests.forEach(test); + console.log(''); + }); +} + +/** + * Inherit from `Base.prototype`. + */ + +function F(){}; +F.prototype = Base.prototype; +XUnit.prototype = new F; +XUnit.prototype.constructor = XUnit; + + +/** + * Output tag for the given `test.` + */ + +function test(test) { + var attrs = { + classname: test.parent.fullTitle() + , name: test.title + , time: test.duration / 1000 + }; + + if ('failed' == test.state) { + var err = test.err; + attrs.message = escape(err.message); + console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); + } else if (test.pending) { + console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); + } else { + console.log(tag('testcase', attrs, true) ); + } +} + +/** + * HTML tag helper. + */ + +function tag(name, attrs, close, content) { + var end = close ? '/>' : '>' + , pairs = [] + , tag; + + for (var key in attrs) { + pairs.push(key + '="' + escape(attrs[key]) + '"'); + } + + tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; + if (content) tag += content + ''; +} + +}); // module: reporters/xunit.js + +require.register("runnable.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runnable') + , milliseconds = require('./ms'); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date + , setTimeout = global.setTimeout + , setInterval = global.setInterval + , clearTimeout = global.clearTimeout + , clearInterval = global.clearInterval; + +/** + * Object#toString(). + */ + +var toString = Object.prototype.toString; + +/** + * Expose `Runnable`. + */ + +module.exports = Runnable; + +/** + * Initialize a new `Runnable` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Runnable(title, fn) { + this.title = title; + this.fn = fn; + this.async = fn && fn.length; + this.sync = ! this.async; + this._timeout = 2000; + this._slow = 75; + this.timedOut = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runnable.prototype = new F; +Runnable.prototype.constructor = Runnable; + + +/** + * Set & get timeout `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = ms; + if (this.timer) this.resetTimeout(); + return this; +}; + +/** + * Set & get slow `ms`. + * + * @param {Number|String} ms + * @return {Runnable|Number} ms or self + * @api private + */ + +Runnable.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._slow = ms; + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Runnable.prototype.fullTitle = function(){ + return this.parent.fullTitle() + ' ' + this.title; +}; + +/** + * Clear the timeout. + * + * @api private + */ + +Runnable.prototype.clearTimeout = function(){ + clearTimeout(this.timer); +}; + +/** + * Inspect the runnable void of private properties. + * + * @return {String} + * @api private + */ + +Runnable.prototype.inspect = function(){ + return JSON.stringify(this, function(key, val){ + if ('_' == key[0]) return; + if ('parent' == key) return '#'; + if ('ctx' == key) return '#'; + return val; + }, 2); +}; + +/** + * Reset the timeout. + * + * @api private + */ + +Runnable.prototype.resetTimeout = function(){ + var self = this; + var ms = this.timeout() || 1e9; + + this.clearTimeout(); + this.timer = setTimeout(function(){ + self.callback(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); +}; + +/** + * Run the test and invoke `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runnable.prototype.run = function(fn){ + var self = this + , ms = this.timeout() + , start = new Date + , ctx = this.ctx + , finished + , emitted; + + if (ctx) ctx.runnable(this); + + // timeout + if (this.async) { + if (ms) { + this.timer = setTimeout(function(){ + done(new Error('timeout of ' + ms + 'ms exceeded')); + self.timedOut = true; + }, ms); + } + } + + // called multiple times + function multiple(err) { + if (emitted) return; + emitted = true; + self.emit('error', err || new Error('done() called multiple times')); + } + + // finished + function done(err) { + if (self.timedOut) return; + if (finished) return multiple(err); + self.clearTimeout(); + self.duration = new Date - start; + finished = true; + fn(err); + } + + // for .resetTimeout() + this.callback = done; + + // async + if (this.async) { + try { + this.fn.call(ctx, function(err){ + if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); + if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); + done(); + }); + } catch (err) { + done(err); + } + return; + } + + if (this.asyncOnly) { + return done(new Error('--async-only option in use without declaring `done()`')); + } + + // sync + try { + if (!this.pending) this.fn.call(ctx); + this.duration = new Date - start; + fn(); + } catch (err) { + fn(err); + } +}; + +}); // module: runnable.js + +require.register("runner.js", function(module, exports, require){ +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:runner') + , Test = require('./test') + , utils = require('./utils') + , filter = utils.filter + , keys = utils.keys; + +/** + * Non-enumerable globals. + */ + +var globals = [ + 'setTimeout', + 'clearTimeout', + 'setInterval', + 'clearInterval', + 'XMLHttpRequest', + 'Date' +]; + +/** + * Expose `Runner`. + */ + +module.exports = Runner; + +/** + * Initialize a `Runner` for the given `suite`. + * + * Events: + * + * - `start` execution started + * - `end` execution complete + * - `suite` (suite) test suite execution started + * - `suite end` (suite) all tests (and sub-suites) have finished + * - `test` (test) test execution started + * - `test end` (test) test completed + * - `hook` (hook) hook execution started + * - `hook end` (hook) hook complete + * - `pass` (test) test passed + * - `fail` (test, err) test failed + * - `pending` (test) test pending + * + * @api public + */ + +function Runner(suite) { + var self = this; + this._globals = []; + this.suite = suite; + this.total = suite.total(); + this.failures = 0; + this.on('test end', function(test){ self.checkGlobals(test); }); + this.on('hook end', function(hook){ self.checkGlobals(hook); }); + this.grep(/.*/); + this.globals(this.globalProps().concat(['errno'])); +} + +/** + * Wrapper for setImmediate, process.nextTick, or browser polyfill. + * + * @param {Function} fn + * @api private + */ + +Runner.immediately = global.setImmediate || process.nextTick; + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Runner.prototype = new F; +Runner.prototype.constructor = Runner; + + +/** + * Run tests with full titles matching `re`. Updates runner.total + * with number of tests matched. + * + * @param {RegExp} re + * @param {Boolean} invert + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.grep = function(re, invert){ + debug('grep %s', re); + this._grep = re; + this._invert = invert; + this.total = this.grepTotal(this.suite); + return this; +}; + +/** + * Returns the number of tests matching the grep search for the + * given suite. + * + * @param {Suite} suite + * @return {Number} + * @api public + */ + +Runner.prototype.grepTotal = function(suite) { + var self = this; + var total = 0; + + suite.eachTest(function(test){ + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (match) total++; + }); + + return total; +}; + +/** + * Return a list of global properties. + * + * @return {Array} + * @api private + */ + +Runner.prototype.globalProps = function() { + var props = utils.keys(global); + + // non-enumerables + for (var i = 0; i < globals.length; ++i) { + if (~utils.indexOf(props, globals[i])) continue; + props.push(globals[i]); + } + + return props; +}; + +/** + * Allow the given `arr` of globals. + * + * @param {Array} arr + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.globals = function(arr){ + if (0 == arguments.length) return this._globals; + debug('globals %j', arr); + utils.forEach(arr, function(arr){ + this._globals.push(arr); + }, this); + return this; +}; + +/** + * Check for global variable leaks. + * + * @api private + */ + +Runner.prototype.checkGlobals = function(test){ + if (this.ignoreLeaks) return; + var ok = this._globals; + var globals = this.globalProps(); + var isNode = process.kill; + var leaks; + + // check length - 2 ('errno' and 'location' globals) + if (isNode && 1 == ok.length - globals.length) return + else if (2 == ok.length - globals.length) return; + + leaks = filterLeaks(ok, globals); + this._globals = this._globals.concat(leaks); + + if (leaks.length > 1) { + this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); + } else if (leaks.length) { + this.fail(test, new Error('global leak detected: ' + leaks[0])); + } +}; + +/** + * Fail the given `test`. + * + * @param {Test} test + * @param {Error} err + * @api private + */ + +Runner.prototype.fail = function(test, err){ + ++this.failures; + test.state = 'failed'; + + if ('string' == typeof err) { + err = new Error('the string "' + err + '" was thrown, throw an Error :)'); + } + + this.emit('fail', test, err); +}; + +/** + * Fail the given `hook` with `err`. + * + * Hook failures (currently) hard-end due + * to that fact that a failing hook will + * surely cause subsequent tests to fail, + * causing jumbled reporting. + * + * @param {Hook} hook + * @param {Error} err + * @api private + */ + +Runner.prototype.failHook = function(hook, err){ + this.fail(hook, err); + this.emit('end'); +}; + +/** + * Run hook `name` callbacks and then invoke `fn()`. + * + * @param {String} name + * @param {Function} function + * @api private + */ + +Runner.prototype.hook = function(name, fn){ + var suite = this.suite + , hooks = suite['_' + name] + , self = this + , timer; + + function next(i) { + var hook = hooks[i]; + if (!hook) return fn(); + if (self.failures && suite.bail()) return fn(); + self.currentRunnable = hook; + + hook.ctx.currentTest = self.test; + + self.emit('hook', hook); + + hook.on('error', function(err){ + self.failHook(hook, err); + }); + + hook.run(function(err){ + hook.removeAllListeners('error'); + var testError = hook.error(); + if (testError) self.fail(self.test, testError); + if (err) return self.failHook(hook, err); + self.emit('hook end', hook); + delete hook.ctx.currentTest; + next(++i); + }); + } + + Runner.immediately(function(){ + next(0); + }); +}; + +/** + * Run hook `name` for the given array of `suites` + * in order, and callback `fn(err)`. + * + * @param {String} name + * @param {Array} suites + * @param {Function} fn + * @api private + */ + +Runner.prototype.hooks = function(name, suites, fn){ + var self = this + , orig = this.suite; + + function next(suite) { + self.suite = suite; + + if (!suite) { + self.suite = orig; + return fn(); + } + + self.hook(name, function(err){ + if (err) { + self.suite = orig; + return fn(err); + } + + next(suites.pop()); + }); + } + + next(suites.pop()); +}; + +/** + * Run hooks from the top level down. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookUp = function(name, fn){ + var suites = [this.suite].concat(this.parents()).reverse(); + this.hooks(name, suites, fn); +}; + +/** + * Run hooks from the bottom up. + * + * @param {String} name + * @param {Function} fn + * @api private + */ + +Runner.prototype.hookDown = function(name, fn){ + var suites = [this.suite].concat(this.parents()); + this.hooks(name, suites, fn); +}; + +/** + * Return an array of parent Suites from + * closest to furthest. + * + * @return {Array} + * @api private + */ + +Runner.prototype.parents = function(){ + var suite = this.suite + , suites = []; + while (suite = suite.parent) suites.push(suite); + return suites; +}; + +/** + * Run the current test and callback `fn(err)`. + * + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTest = function(fn){ + var test = this.test + , self = this; + + if (this.asyncOnly) test.asyncOnly = true; + + try { + test.on('error', function(err){ + self.fail(test, err); + }); + test.run(fn); + } catch (err) { + fn(err); + } +}; + +/** + * Run tests in the given `suite` and invoke + * the callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runTests = function(suite, fn){ + var self = this + , tests = suite.tests.slice() + , test; + + function next(err) { + // if we bail after first err + if (self.failures && suite._bail) return fn(); + + // next test + test = tests.shift(); + + // all done + if (!test) return fn(); + + // grep + var match = self._grep.test(test.fullTitle()); + if (self._invert) match = !match; + if (!match) return next(); + + // pending + if (test.pending) { + self.emit('pending', test); + self.emit('test end', test); + return next(); + } + + // execute test and hook(s) + self.emit('test', self.test = test); + self.hookDown('beforeEach', function(){ + self.currentRunnable = self.test; + self.runTest(function(err){ + test = self.test; + + if (err) { + self.fail(test, err); + self.emit('test end', test); + return self.hookUp('afterEach', next); + } + + test.state = 'passed'; + self.emit('pass', test); + self.emit('test end', test); + self.hookUp('afterEach', next); + }); + }); + } + + this.next = next; + next(); +}; + +/** + * Run the given `suite` and invoke the + * callback `fn()` when complete. + * + * @param {Suite} suite + * @param {Function} fn + * @api private + */ + +Runner.prototype.runSuite = function(suite, fn){ + var total = this.grepTotal(suite) + , self = this + , i = 0; + + debug('run suite %s', suite.fullTitle()); + + if (!total) return fn(); + + this.emit('suite', this.suite = suite); + + function next() { + var curr = suite.suites[i++]; + if (!curr) return done(); + self.runSuite(curr, next); + } + + function done() { + self.suite = suite; + self.hook('afterAll', function(){ + self.emit('suite end', suite); + fn(); + }); + } + + this.hook('beforeAll', function(){ + self.runTests(suite, next); + }); +}; + +/** + * Handle uncaught exceptions. + * + * @param {Error} err + * @api private + */ + +Runner.prototype.uncaught = function(err){ + debug('uncaught exception %s', err.message); + var runnable = this.currentRunnable; + if (!runnable || 'failed' == runnable.state) return; + runnable.clearTimeout(); + err.uncaught = true; + this.fail(runnable, err); + + // recover from test + if ('test' == runnable.type) { + this.emit('test end', runnable); + this.hookUp('afterEach', this.next); + return; + } + + // bail on hooks + this.emit('end'); +}; + +/** + * Run the root suite and invoke `fn(failures)` + * on completion. + * + * @param {Function} fn + * @return {Runner} for chaining + * @api public + */ + +Runner.prototype.run = function(fn){ + var self = this + , fn = fn || function(){}; + + function uncaught(err){ + self.uncaught(err); + } + + debug('start'); + + // callback + this.on('end', function(){ + debug('end'); + process.removeListener('uncaughtException', uncaught); + fn(self.failures); + }); + + // run suites + this.emit('start'); + this.runSuite(this.suite, function(){ + debug('finished running'); + self.emit('end'); + }); + + // uncaught exception + process.on('uncaughtException', uncaught); + + return this; +}; + +/** + * Filter leaks with the given globals flagged as `ok`. + * + * @param {Array} ok + * @param {Array} globals + * @return {Array} + * @api private + */ + +function filterLeaks(ok, globals) { + return filter(globals, function(key){ + // Firefox and Chrome exposes iframes as index inside the window object + if (/^d+/.test(key)) return false; + var matched = filter(ok, function(ok){ + if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); + // Opera and IE expose global variables for HTML element IDs (issue #243) + if (/^mocha-/.test(key)) return true; + return key == ok; + }); + return matched.length == 0 && (!global.navigator || 'onerror' !== key); + }); +} + +}); // module: runner.js + +require.register("suite.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var EventEmitter = require('browser/events').EventEmitter + , debug = require('browser/debug')('mocha:suite') + , milliseconds = require('./ms') + , utils = require('./utils') + , Hook = require('./hook'); + +/** + * Expose `Suite`. + */ + +exports = module.exports = Suite; + +/** + * Create a new `Suite` with the given `title` + * and parent `Suite`. When a suite with the + * same title is already present, that suite + * is returned to provide nicer reporter + * and more flexible meta-testing. + * + * @param {Suite} parent + * @param {String} title + * @return {Suite} + * @api public + */ + +exports.create = function(parent, title){ + var suite = new Suite(title, parent.ctx); + suite.parent = parent; + if (parent.pending) suite.pending = true; + title = suite.fullTitle(); + parent.addSuite(suite); + return suite; +}; + +/** + * Initialize a new `Suite` with the given + * `title` and `ctx`. + * + * @param {String} title + * @param {Context} ctx + * @api private + */ + +function Suite(title, ctx) { + this.title = title; + this.ctx = ctx; + this.suites = []; + this.tests = []; + this.pending = false; + this._beforeEach = []; + this._beforeAll = []; + this._afterEach = []; + this._afterAll = []; + this.root = !title; + this._timeout = 2000; + this._slow = 75; + this._bail = false; +} + +/** + * Inherit from `EventEmitter.prototype`. + */ + +function F(){}; +F.prototype = EventEmitter.prototype; +Suite.prototype = new F; +Suite.prototype.constructor = Suite; + + +/** + * Return a clone of this `Suite`. + * + * @return {Suite} + * @api private + */ + +Suite.prototype.clone = function(){ + var suite = new Suite(this.title); + debug('clone'); + suite.ctx = this.ctx; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + return suite; +}; + +/** + * Set timeout `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.timeout = function(ms){ + if (0 == arguments.length) return this._timeout; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('timeout %d', ms); + this._timeout = parseInt(ms, 10); + return this; +}; + +/** + * Set slow `ms` or short-hand such as "2s". + * + * @param {Number|String} ms + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.slow = function(ms){ + if (0 === arguments.length) return this._slow; + if ('string' == typeof ms) ms = milliseconds(ms); + debug('slow %d', ms); + this._slow = ms; + return this; +}; + +/** + * Sets whether to bail after first error. + * + * @parma {Boolean} bail + * @return {Suite|Number} for chaining + * @api private + */ + +Suite.prototype.bail = function(bail){ + if (0 == arguments.length) return this._bail; + debug('bail %s', bail); + this._bail = bail; + return this; +}; + +/** + * Run `fn(test[, done])` before running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeAll.push(hook); + this.emit('beforeAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after running tests. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterAll = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after all" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterAll.push(hook); + this.emit('afterAll', hook); + return this; +}; + +/** + * Run `fn(test[, done])` before each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.beforeEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"before each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._beforeEach.push(hook); + this.emit('beforeEach', hook); + return this; +}; + +/** + * Run `fn(test[, done])` after each test case. + * + * @param {Function} fn + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.afterEach = function(fn){ + if (this.pending) return this; + var hook = new Hook('"after each" hook', fn); + hook.parent = this; + hook.timeout(this.timeout()); + hook.slow(this.slow()); + hook.ctx = this.ctx; + this._afterEach.push(hook); + this.emit('afterEach', hook); + return this; +}; + +/** + * Add a test `suite`. + * + * @param {Suite} suite + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addSuite = function(suite){ + suite.parent = this; + suite.timeout(this.timeout()); + suite.slow(this.slow()); + suite.bail(this.bail()); + this.suites.push(suite); + this.emit('suite', suite); + return this; +}; + +/** + * Add a `test` to this suite. + * + * @param {Test} test + * @return {Suite} for chaining + * @api private + */ + +Suite.prototype.addTest = function(test){ + test.parent = this; + test.timeout(this.timeout()); + test.slow(this.slow()); + test.ctx = this.ctx; + this.tests.push(test); + this.emit('test', test); + return this; +}; + +/** + * Return the full title generated by recursively + * concatenating the parent's full title. + * + * @return {String} + * @api public + */ + +Suite.prototype.fullTitle = function(){ + if (this.parent) { + var full = this.parent.fullTitle(); + if (full) return full + ' ' + this.title; + } + return this.title; +}; + +/** + * Return the total number of tests. + * + * @return {Number} + * @api public + */ + +Suite.prototype.total = function(){ + return utils.reduce(this.suites, function(sum, suite){ + return sum + suite.total(); + }, 0) + this.tests.length; +}; + +/** + * Iterates through each suite recursively to find + * all tests. Applies a function in the format + * `fn(test)`. + * + * @param {Function} fn + * @return {Suite} + * @api private + */ + +Suite.prototype.eachTest = function(fn){ + utils.forEach(this.tests, fn); + utils.forEach(this.suites, function(suite){ + suite.eachTest(fn); + }); + return this; +}; + +}); // module: suite.js + +require.register("test.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var Runnable = require('./runnable'); + +/** + * Expose `Test`. + */ + +module.exports = Test; + +/** + * Initialize a new `Test` with the given `title` and callback `fn`. + * + * @param {String} title + * @param {Function} fn + * @api private + */ + +function Test(title, fn) { + Runnable.call(this, title, fn); + this.pending = !fn; + this.type = 'test'; +} + +/** + * Inherit from `Runnable.prototype`. + */ + +function F(){}; +F.prototype = Runnable.prototype; +Test.prototype = new F; +Test.prototype.constructor = Test; + + +}); // module: test.js + +require.register("utils.js", function(module, exports, require){ + +/** + * Module dependencies. + */ + +var fs = require('browser/fs') + , path = require('browser/path') + , join = path.join + , debug = require('browser/debug')('mocha:watch'); + +/** + * Ignored directories. + */ + +var ignore = ['node_modules', '.git']; + +/** + * Escape special characters in the given string of html. + * + * @param {String} html + * @return {String} + * @api private + */ + +exports.escape = function(html){ + return String(html) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +}; + +/** + * Array#forEach (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} scope + * @api private + */ + +exports.forEach = function(arr, fn, scope){ + for (var i = 0, l = arr.length; i < l; i++) + fn.call(scope, arr[i], i); +}; + +/** + * Array#indexOf (<=IE8) + * + * @parma {Array} arr + * @param {Object} obj to find index of + * @param {Number} start + * @api private + */ + +exports.indexOf = function(arr, obj, start){ + for (var i = start || 0, l = arr.length; i < l; i++) { + if (arr[i] === obj) + return i; + } + return -1; +}; + +/** + * Array#reduce (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @param {Object} initial value + * @api private + */ + +exports.reduce = function(arr, fn, val){ + var rval = val; + + for (var i = 0, l = arr.length; i < l; i++) { + rval = fn(rval, arr[i], i, arr); + } + + return rval; +}; + +/** + * Array#filter (<=IE8) + * + * @param {Array} array + * @param {Function} fn + * @api private + */ + +exports.filter = function(arr, fn){ + var ret = []; + + for (var i = 0, l = arr.length; i < l; i++) { + var val = arr[i]; + if (fn(val, i, arr)) ret.push(val); + } + + return ret; +}; + +/** + * Object.keys (<=IE8) + * + * @param {Object} obj + * @return {Array} keys + * @api private + */ + +exports.keys = Object.keys || function(obj) { + var keys = [] + , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 + + for (var key in obj) { + if (has.call(obj, key)) { + keys.push(key); + } + } + + return keys; +}; + +/** + * Watch the given `files` for changes + * and invoke `fn(file)` on modification. + * + * @param {Array} files + * @param {Function} fn + * @api private + */ + +exports.watch = function(files, fn){ + var options = { interval: 100 }; + files.forEach(function(file){ + debug('file %s', file); + fs.watchFile(file, options, function(curr, prev){ + if (prev.mtime < curr.mtime) fn(file); + }); + }); +}; + +/** + * Ignored files. + */ + +function ignored(path){ + return !~ignore.indexOf(path); +} + +/** + * Lookup files in the given `dir`. + * + * @return {Array} + * @api private + */ + +exports.files = function(dir, ret){ + ret = ret || []; + + fs.readdirSync(dir) + .filter(ignored) + .forEach(function(path){ + path = join(dir, path); + if (fs.statSync(path).isDirectory()) { + exports.files(path, ret); + } else if (path.match(/\.(js|coffee)$/)) { + ret.push(path); + } + }); + + return ret; +}; + +/** + * Compute a slug from the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.slug = function(str){ + return str + .toLowerCase() + .replace(/ +/g, '-') + .replace(/[^-\w]/g, ''); +}; + +/** + * Strip the function definition from `str`, + * and re-indent for pre whitespace. + */ + +exports.clean = function(str) { + str = str + .replace(/^function *\(.*\) *{/, '') + .replace(/\s+\}$/, ''); + + var whitespace = str.match(/^\n?(\s*)/)[1] + , re = new RegExp('^' + whitespace, 'gm'); + + str = str.replace(re, ''); + + return exports.trim(str); +}; + +/** + * Escape regular expression characters in `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.escapeRegexp = function(str){ + return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); +}; + +/** + * Trim the given `str`. + * + * @param {String} str + * @return {String} + * @api private + */ + +exports.trim = function(str){ + return str.replace(/^\s+|\s+$/g, ''); +}; + +/** + * Parse the given `qs`. + * + * @param {String} qs + * @return {Object} + * @api private + */ + +exports.parseQuery = function(qs){ + return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ + var i = pair.indexOf('=') + , key = pair.slice(0, i) + , val = pair.slice(++i); + + obj[key] = decodeURIComponent(val); + return obj; + }, {}); +}; + +/** + * Highlight the given string of `js`. + * + * @param {String} js + * @return {String} + * @api private + */ + +function highlight(js) { + return js + .replace(//g, '>') + .replace(/\/\/(.*)/gm, '//$1') + .replace(/('.*?')/gm, '$1') + .replace(/(\d+\.\d+)/gm, '$1') + .replace(/(\d+)/gm, '$1') + .replace(/\bnew *(\w+)/gm, 'new $1') + .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') +} + +/** + * Highlight the contents of tag `name`. + * + * @param {String} name + * @api private + */ + +exports.highlightTags = function(name) { + var code = document.getElementsByTagName(name); + for (var i = 0, len = code.length; i < len; ++i) { + code[i].innerHTML = highlight(code[i].innerHTML); + } +}; + +}); // module: utils.js +// The global object is "self" in Web Workers. +global = (function() { return this; })(); + +/** + * Save timer references to avoid Sinon interfering (see GH-237). + */ + +var Date = global.Date; +var setTimeout = global.setTimeout; +var setInterval = global.setInterval; +var clearTimeout = global.clearTimeout; +var clearInterval = global.clearInterval; + +/** + * Node shims. + * + * These are meant only to allow + * mocha.js to run untouched, not + * to allow running node code in + * the browser. + */ + +var process = {}; +process.exit = function(status){}; +process.stdout = {}; + +/** + * Remove uncaughtException listener. + */ + +process.removeListener = function(e){ + if ('uncaughtException' == e) { + global.onerror = function() {}; + } +}; + +/** + * Implements uncaughtException listener. + */ + +process.on = function(e, fn){ + if ('uncaughtException' == e) { + global.onerror = function(err, url, line){ + fn(new Error(err + ' (' + url + ':' + line + ')')); + }; + } +}; + +/** + * Expose mocha. + */ + +var Mocha = global.Mocha = require('mocha'), + mocha = global.mocha = new Mocha({ reporter: 'html' }); + +var immediateQueue = [] + , immediateTimeout; + +function timeslice() { + var immediateStart = new Date().getTime(); + while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { + immediateQueue.shift()(); + } + if (immediateQueue.length) { + immediateTimeout = setTimeout(timeslice, 0); + } else { + immediateTimeout = null; + } +} + +/** + * High-performance override of Runner.immediately. + */ + +Mocha.Runner.immediately = function(callback) { + immediateQueue.push(callback); + if (!immediateTimeout) { + immediateTimeout = setTimeout(timeslice, 0); + } +}; + +/** + * Override ui to ensure that the ui functions are initialized. + * Normally this would happen in Mocha.prototype.loadFiles. + */ + +mocha.ui = function(ui){ + Mocha.prototype.ui.call(this, ui); + this.suite.emit('pre-require', global, null, this); + return this; +}; + +/** + * Setup mocha with the given setting options. + */ + +mocha.setup = function(opts){ + if ('string' == typeof opts) opts = { ui: opts }; + for (var opt in opts) this[opt](opts[opt]); + return this; +}; + +/** + * Run mocha, returning the Runner. + */ + +mocha.run = function(fn){ + var options = mocha.options; + mocha.globals('location'); + + var query = Mocha.utils.parseQuery(global.location.search || ''); + if (query.grep) mocha.grep(query.grep); + if (query.invert) mocha.invert(); + + return Mocha.prototype.run.call(mocha, function(){ + // The DOM Document is not available in Web Workers. + if (global.document) { + Mocha.utils.highlightTags('code'); + } + if (fn) fn(); + }); +}; + +/** + * Expose the process shim. + */ + +Mocha.process = process; +})(); \ No newline at end of file diff --git a/test.new/static/js/proclaim.js b/test.new/static/js/proclaim.js new file mode 100644 index 000000000..8ec76cee2 --- /dev/null +++ b/test.new/static/js/proclaim.js @@ -0,0 +1,528 @@ +/* global define */ +(function (root) { + 'use strict'; + + // NOTE: One change has been made from stock proclaim.js: it now + // explicitly puts the fallback message (which had previously only been + // included in AssertionError#toString) into the error's `message` + // property. Without this, a failed test would display `undefined` in + // Mocha unless it had its own message. -APD + + var proclaim = ok; + + // Assertions as outlined in + // http://wiki.commonjs.org/wiki/Unit_Testing/1.0#Assert + // ----------------------------------------------------- + + // Assert that a value is truthy + function ok (val, msg) { + if (!!!val) { + fail(val, true, msg, '=='); + } + } + proclaim.ok = ok; + + // Assert that two values are equal + proclaim.equal = function (actual, expected, msg) { + /* jshint eqeqeq: false */ + if (actual != expected) { + fail(actual, expected, msg, '=='); + } + }; + + // Assert that two values are not equal + proclaim.notEqual = function (actual, expected, msg) { + /* jshint eqeqeq: false */ + if (actual == expected) { + fail(actual, expected, msg, '!='); + } + }; + + // Assert that two values are equal with strict comparison + proclaim.strictEqual = function (actual, expected, msg) { + if (actual !== expected) { + fail(actual, expected, msg, '==='); + } + }; + + // Assert that two values are not equal with strict comparison + proclaim.notStrictEqual = function (actual, expected, msg) { + if (actual === expected) { + fail(actual, expected, msg, '!=='); + } + }; + + // Assert that two values are deeply equal + proclaim.deepEqual = function (actual, expected, msg) { + if (!isDeepEqual(actual, expected)) { + fail(actual, expected, msg, 'deepEqual'); + } + }; + + // Assert that two values are not deeply equal + proclaim.notDeepEqual = function (actual, expected, msg) { + if (isDeepEqual(actual, expected)) { + fail(actual, expected, msg, '!deepEqual'); + } + }; + + // Assert that a function throws an error + proclaim['throws'] = function (fn, expected, msg) { + if (!functionThrows(fn, expected)) { + fail(fn, expected, msg, 'throws'); + } + }; + + + // Additional assertions + // --------------------- + + // Assert that a value is falsy + proclaim.notOk = function (val, msg) { + if (!!val) { + fail(val, true, msg, '!='); + } + }; + + // Assert that a function does not throw an error + proclaim.doesNotThrow = function (fn, expected, msg) { + if (functionThrows(fn, expected)) { + fail(fn, expected, msg, '!throws'); + } + }; + + // Assert that a value is a specific type + proclaim.isTypeOf = function (val, type, msg) { + proclaim.strictEqual(typeof val, type, msg); + }; + + // Assert that a value is not a specific type + proclaim.isNotTypeOf = function (val, type, msg) { + proclaim.notStrictEqual(typeof val, type, msg); + }; + + // Assert that a value is an instance of a constructor + proclaim.isInstanceOf = function (val, constructor, msg) { + if (!(val instanceof constructor)) { + fail(val, constructor, msg, 'instanceof'); + } + }; + + // Assert that a value not an instance of a constructor + proclaim.isNotInstanceOf = function (val, constructor, msg) { + if (val instanceof constructor) { + fail(val, constructor, msg, '!instanceof'); + } + }; + + // Assert that a value is an array + proclaim.isArray = function (val, msg) { + if (!isArray(val)) { + fail(typeof val, 'array', msg, '==='); + } + }; + + // Assert that a value is not an array + proclaim.isNotArray = function (val, msg) { + if (isArray(val)) { + fail(typeof val, 'array', msg, '!=='); + } + }; + + // Assert that a value is a boolean + proclaim.isBoolean = function (val, msg) { + proclaim.isTypeOf(val, 'boolean', msg); + }; + + // Assert that a value is not a boolean + proclaim.isNotBoolean = function (val, msg) { + proclaim.isNotTypeOf(val, 'boolean', msg); + }; + + // Assert that a value is true + proclaim.isTrue = function (val, msg) { + proclaim.strictEqual(val, true, msg); + }; + + // Assert that a value is false + proclaim.isFalse = function (val, msg) { + proclaim.strictEqual(val, false, msg); + }; + + // Assert that a value is a function + proclaim.isFunction = function (val, msg) { + proclaim.isTypeOf(val, 'function', msg); + }; + + // Assert that a value is not a function + proclaim.isNotFunction = function (val, msg) { + proclaim.isNotTypeOf(val, 'function', msg); + }; + + // Assert that a value is null + proclaim.isNull = function (val, msg) { + proclaim.strictEqual(val, null, msg); + }; + + // Assert that a value is not null + proclaim.isNotNull = function (val, msg) { + proclaim.notStrictEqual(val, null, msg); + }; + + // Assert that a value is a number + proclaim.isNumber = function (val, msg) { + proclaim.isTypeOf(val, 'number', msg); + }; + + // Assert that a value is not a number + proclaim.isNotNumber = function (val, msg) { + proclaim.isNotTypeOf(val, 'number', msg); + }; + + // Assert that a value is an object + proclaim.isObject = function (val, msg) { + proclaim.isTypeOf(val, 'object', msg); + }; + + // Assert that a value is not an object + proclaim.isNotObject = function (val, msg) { + proclaim.isNotTypeOf(val, 'object', msg); + }; + + // Assert that a value is a string + proclaim.isString = function (val, msg) { + proclaim.isTypeOf(val, 'string', msg); + }; + + // Assert that a value is not a string + proclaim.isNotString = function (val, msg) { + proclaim.isNotTypeOf(val, 'string', msg); + }; + + // Assert that a value is undefined + proclaim.isUndefined = function (val, msg) { + proclaim.isTypeOf(val, 'undefined', msg); + }; + + // Assert that a value is defined + proclaim.isDefined = function (val, msg) { + proclaim.isNotTypeOf(val, 'undefined', msg); + }; + + // Assert that a value matches a regular expression + proclaim.match = function (actual, expected, msg) { + if (!expected.test(actual)) { + fail(actual, expected, msg, 'match'); + } + }; + + // Assert that a value does not match a regular expression + proclaim.notMatch = function (actual, expected, msg) { + if (expected.test(actual)) { + fail(actual, expected, msg, '!match'); + } + }; + + // Assert that an object includes something + proclaim.includes = function (haystack, needle, msg) { + if (!includes(haystack, needle)) { + fail(haystack, needle, msg, 'include'); + } + }; + + // Assert that an object does not include something + proclaim.doesNotInclude = function (haystack, needle, msg) { + if (includes(haystack, needle)) { + fail(haystack, needle, msg, '!include'); + } + }; + + // Assert that an object (Array, String, etc.) has the expected length + proclaim.lengthEquals = function (obj, expected, msg) { + var undef; + if (isUndefinedOrNull(obj)) { + return fail(undef, expected, msg, 'length'); + } + if (obj.length !== expected) { + fail(obj.length, expected, msg, 'length'); + } + }; + + // Assert that a value is less than another value + proclaim.lessThan = function (actual, expected, msg) { + if (actual >= expected) { + fail(actual, expected, msg, '<'); + } + }; + + // Assert that a value is less than or equal to another value + proclaim.lessThanOrEqual = function (actual, expected, msg) { + if (actual > expected) { + fail(actual, expected, msg, '<='); + } + }; + + // Assert that a value is greater than another value + proclaim.greaterThan = function (actual, expected, msg) { + if (actual <= expected) { + fail(actual, expected, msg, '>'); + } + }; + + // Assert that a value is greater than another value + proclaim.greaterThanOrEqual = function (actual, expected, msg) { + if (actual < expected) { + fail(actual, expected, msg, '>='); + } + }; + + + // Error handling + // -------------- + + // Assertion error class + function AssertionError (opts) { + opts = opts || {}; + this.name = 'AssertionError'; + this.actual = opts.actual; + this.expected = opts.expected; + this.operator = opts.operator || ''; + this._message = opts.message; + + this.message = this._describe(); + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, opts.stackStartFunction || fail); + } + } + AssertionError.prototype = (Object.create ? Object.create(Error.prototype) : new Error()); + AssertionError.prototype.name = 'AssertionError'; + AssertionError.prototype.constructor = AssertionError; + + AssertionError.prototype._describe = function () { + var output = this.actual + ' ' + + this.operator + ' ' + + this.expected; + + return this._message ? this._message + ': ' + output : output; + }; + + // Assertion error to string + AssertionError.prototype.toString = function () { + return this.name + ': ' + this._describe(); + }; + + // Fail a test + function fail (actual, expected, message, operator, stackStartFunction) { + throw new AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); + } + + // Expose error handling tools + proclaim.AssertionError = AssertionError; + proclaim.fail = fail; + + + // Utilities + // --------- + + // Utility for checking whether a value is undefined or null + function isUndefinedOrNull (val) { + return (val === null || typeof val === 'undefined'); + } + + // Utility for checking whether a value is an arguments object + function isArgumentsObject (val) { + return (Object.prototype.toString.call(val) === '[object Arguments]'); + } + + // Utility for checking whether a value is plain object + function isPlainObject (val) { + return Object.prototype.toString.call(val) === '[object Object]'; + } + + // Utility for checking whether an object contains another object + function includes (haystack, needle) { + /* jshint maxdepth: 3*/ + var i; + + // Array#indexOf, but ie... + if (isArray(haystack)) { + for (i = haystack.length - 1; i >= 0; i = i - 1) { + if (haystack[i] === needle) { + return true; + } + } + } + + // String#indexOf + if (typeof haystack === 'string') { + if (haystack.indexOf(needle) !== -1) { + return true; + } + } + + // Object#hasOwnProperty + if (isPlainObject(haystack)) { + if (haystack.hasOwnProperty(needle)) { + return true; + } + } + + return false; + } + + // Utility for checking whether a value is an array + var isArray = Array.isArray || function (val) { + return (Object.prototype.toString.call(val) === '[object Array]'); + }; + + // Utility for getting object keys + function getObjectKeys (obj) { + var key, keys = []; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + } + + // Utility for deep equality testing of objects + function objectsEqual (obj1, obj2) { + /* jshint eqeqeq: false */ + + // Check for undefined or null + if (isUndefinedOrNull(obj1) || isUndefinedOrNull(obj2)) { + return false; + } + + // Object prototypes must be the same + if (obj1.prototype !== obj2.prototype) { + return false; + } + + // Handle argument objects + if (isArgumentsObject(obj1)) { + if (!isArgumentsObject(obj2)) { + return false; + } + obj1 = Array.prototype.slice.call(obj1); + obj2 = Array.prototype.slice.call(obj2); + } + + // Check number of own properties + var obj1Keys = getObjectKeys(obj1); + var obj2Keys = getObjectKeys(obj2); + if (obj1Keys.length !== obj2Keys.length) { + return false; + } + + obj1Keys.sort(); + obj2Keys.sort(); + + // Cheap initial key test (see https://github.com/joyent/node/blob/master/lib/assert.js) + var key, i, len = obj1Keys.length; + for (i = 0; i < len; i += 1) { + if (obj1Keys[i] != obj2Keys[i]) { + return false; + } + } + + // Expensive deep test + for (i = 0; i < len; i += 1) { + key = obj1Keys[i]; + if (!isDeepEqual(obj1[key], obj2[key])) { + return false; + } + } + + // If it got this far... + return true; + } + + // Utility for deep equality testing + function isDeepEqual (actual, expected) { + /* jshint eqeqeq: false */ + if (actual === expected) { + return true; + } + if (expected instanceof Date && actual instanceof Date) { + return actual.getTime() === expected.getTime(); + } + if (actual instanceof RegExp && expected instanceof RegExp) { + return ( + actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase + ); + } + if (typeof actual !== 'object' && typeof expected !== 'object') { + return actual == expected; + } + return objectsEqual(actual, expected); + } + + // Utility for testing whether a function throws an error + function functionThrows (fn, expected) { + + // Try/catch + var thrown = false; + var thrownError; + try { + fn(); + } catch (err) { + thrown = true; + thrownError = err; + } + + // Check error + if (thrown && expected) { + thrown = errorMatches(thrownError, expected); + } + + return thrown; + } + + // Utility for checking whether an error matches a given constructor, regexp or string + function errorMatches (actual, expected) { + if (typeof expected === 'string') { + return actual.message === expected; + } + if (expected instanceof RegExp) { + return expected.test(actual.message); + } + if (actual instanceof expected) { + return true; + } + return false; + } + + + // Exports + // ------- + + // AMD + if (typeof define !== 'undefined' && define.amd) { + define([], function () { + return proclaim; + }); + } + // CommonJS + else if (typeof module !== 'undefined' && module.exports) { + module.exports = proclaim; + } + // Script tag + else { + root.proclaim = proclaim; + } + + +} (this)); \ No newline at end of file diff --git a/test.new/static/js/test_helpers.js b/test.new/static/js/test_helpers.js new file mode 100644 index 000000000..0c5b02f48 --- /dev/null +++ b/test.new/static/js/test_helpers.js @@ -0,0 +1,273 @@ + +(function () { + + function info() { + console.log.apply(console, arguments); + } + + // Fall back when we don't have the console. + // TODO: Find a different way of logging in old IEs. + if (!('console' in window) || !console.log) { + info = function () {}; + } + window.info = info; + + // A function that acts like setTimeout, except with arguments reversed. This + // is far more readable within tests. + function wait(duration, fn) { + return setTimeout(fn, duration); + } + window.wait = wait; + + function buildMessage() { + var args = $A(arguments), message = args.shift(), template = args.shift(); + var output = template.interpolate(args.map(Object.inspect)); + return message ? (message + ': ' + output) : output; + } + + window.assert = proclaim; + + // Add our own assertions. + // + Object.extend(assert, { + enumEqual: function(expected, actual, message) { + expected = $A(expected); + actual = $A(actual); + + message = buildMessage( + message || 'assert.enumEqual', + 'expected collection #{0} to match collection #{1}', + actual, + expected + ); + + var passes = expected.length == actual.length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1]; }); + + if (!passes) { + assert.fail(actual, expected, message, 'enumEqual'); + } + }, + + identical: function (expected, actual, message) { + assert(expected === actual, message); + }, + + notIdentical: function (expected, actual, message) { + assert(expected !== actual, message); + }, + + enabled: function () { + for (var i = 0, element; element = arguments[i]; i++) { + assert( + !$(element).disabled, + 'element was disabled: ' + Object.inspect(element) + ); + } + }, + + disabled: function () { + for (var i = 0, element; element = arguments[i]; i++) { + assert( + $(element).disabled, + 'element was enabled: ' + Object.inspect(element) + ); + } + }, + + raise: function (exceptionName, fn, message) { + var raised = false; + try { + fn(); + } catch (e) { + if (e.name == exceptionName) + raised = true; + } + + assert(raised, message); + }, + + nothingRaised: function (fn, message) { + var raised = false; + try { + fn(); + } catch (e) { + raised = true; + } + + assert(!raised, message); + }, + + respondsTo: function (method, obj, message) { + message = (message || 'assertRespondsTo') + + ": object doesn't respond to <" + method + ">"; + var passes = (method in obj) && (typeof obj[method] === 'function'); + assert(passes, message); + }, + + elementsMatch: function () { + var message, passes = true, expressions = $A(arguments), elements = $A(expressions.shift()); + + if (elements.length !== expressions.length) { + passes = false; + message = 'Size mismatch: #{0} elements, #{1} expressions (#{2})'.interpolate( + [elements.length, expressions.length, expressions]); + } else { + elements.zip(expressions).all(function (pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + + message = 'In index <#{0}>: expected <#{1}> but got #{2}'.interpolate( + [index, expression, Object.inspect(element)]); + passes = false; + }.bind(this)); + } + + assert(passes, message); + }, + + elementMatches: function (element, expression, message) { + this.elementsMatch([element], expression); + }, + + hashEqual: function (expected, actual, message) { + function assertPairEqual(pair) { + return pair.all(Object.isArray) ? + pair[0].zip(pair[1]).all(assertPairEqual) : pair[0] == pair[1]; + } + + expected = $H(expected); + actual = $H(actual); + + var eArray = expected.toArray().sort(); + var aArray = actual.toArray().sort(); + + var passes = (eArray.length === aArray.length) && + eArray.zip(aArray).all(assertPairEqual); + + assert(passes, message); + }, + + hashNotEqual: function (expected, actual, message) { + function assertPairEqual(pair) { + return pair.all(Object.isArray) ? + pair[0].zip(pair[1]).all(assertPairEqual) : pair[0] == pair[1]; + } + + expected = $H(expected); + actual = $H(actual); + + var eArray = expected.toArray().sort(); + var aArray = actual.toArray().sort(); + + var fails = (eArray.length === aArray.length) && + eArray.zip(aArray).all(assertPairEqual); + + assert(!fails, message); + }, + + isNotNullOrUndefined: function (val, message) { + if (val === null || typeof val === 'undefined') { + message = buildMessage( + message, + "expected #{0} not to be null/undefined", + val + ); + assert.fail(val, null, message, 'isNotNullOrUndefined'); + } + } + }); + + + // Add a bit of structure around the tests. + // + // All the tests run on the same page, but each test suite has its own HTML + // fixture. This makes it easy for them to get in each others' way. (The + // Selector tests, especially, are very particular about the types and + // quantities of elements on the page.) + // + // The way we manage this is to assemble all the HTML fixtures before any + // tests run, then detach them all from the document. Then, before a suite + // runs, its fixtures are reattached, then removed again before the next + // suite runs. + window.Test = { + setup: function () { + var body = $(document.body); + this.suites = body.getAttribute('data-suites').split(','); + + this.fixtures = {}; + + this.suites.each(function (suite) { + var fixtures = $('other_fixtures').down('[data-suite="' + suite + '"]'); + if (fixtures) { + this.fixtures[suite] = fixtures.remove();; + } + }, this); + }, + + startSuite: function (suite) { + console.group('Suite:', suite); + if (this.currentFixtures && this.currentFixtures.parentNode) { + this.currentFixtures.remove(); + } + + if (this.fixtures[suite]) { + this.currentFixtures = this.fixtures[suite]; + $('current_fixtures').insert(this.currentFixtures); + } + }, + + endSuite: function (suite) { + console.groupEnd(); + } + }; + + // Wrap Mocha's standard HTML reporter with some logic that pings a URL + // with a results summary once the tests are done. + var HTML = Mocha.reporters.HTML; + + // If there's a `results_url` parameter in the URL, we should use it. + var resultsUrl; + var queryParams = location.search.toQueryParams(); + + if (queryParams['results_url']) { + resultsUrl = queryParams['results_url']; + } + + Test.Reporter = function (runner, root) { + HTML.call(this, runner); + + runner.on('suite end', function (suite) { + if (!suite.root) { + return; + } + + if (!resultsUrl) { + return; + } + + var startMs = this.stats.start.valueOf(); + var endMs = (new Date).valueOf(); + var duration = (endMs - startMs) / 1000; + + var params = { + duration: duration, + tests: this.stats.tests, + passes: this.stats.passes, + failures: this.stats.failures + }; + + var url = resultsUrl + '?' + Object.toQueryString(params); + + var script = document.createElement('script'); + script.src = url; + document.body.appendChild(script); + setTimeout(function () { + document.body.removeChild(script); + }, 2000); + }); + }; + +})(); + + diff --git a/test.new/tests/ajax.test.js b/test.new/tests/ajax.test.js new file mode 100644 index 000000000..caca3ee0d --- /dev/null +++ b/test.new/tests/ajax.test.js @@ -0,0 +1,482 @@ +Fixtures.Ajax = { + js: { + responseBody: '$("content").update("

    Hello world!

    ");', + 'Content-Type': ' text/javascript ' + }, + + html: { + responseBody: "Pack my box with five dozen liquor jugs! " + + "Oh, how quickly daft jumping zebras vex..." + }, + + xml: { + responseBody: 'bar', + 'Content-Type': 'application/xml' + }, + + json: { + responseBody: '{\n\r"test": 123}', + 'Content-Type': 'application/json' + }, + + jsonWithoutContentType: { + responseBody: '{"test": 123}' + }, + + invalidJson: { + responseBody: '{});window.attacked = true;({}', + 'Content-Type': 'application/json' + }, + + headerJson: { + 'X-JSON': '{"test": "hello #éà"}' + } +}; + +var responderCounter = 0; + +// lowercase comparison because of MSIE which presents HTML tags in uppercase +var sentence = ("Pack my box with five dozen liquor jugs! " + + "Oh, how quickly daft jumping zebras vex...").toLowerCase(); + +var message = 'You must be running a test server to test this feature.'; + +function assertContent(id, content, message) { + var a = content.toLowerCase(); + var b = $(id).innerHTML.strip().toLowerCase(); + message = message || 'failure'; + + assert.equal( + a, + b, + message + ': element #' + id + ' should have content: (' + a + ') but has content: (' + b + ')' + ); +} + + +var extendDefault = function(options) { + return Object.extend({ + asynchronous: false, + method: 'get', + onException: function(r, e) { throw e; } + }, options); +}; + +suite("Ajax", function () { + this.timeout(10000); + this.name = 'ajax'; + + setup(function () { + $('content', 'content2').invoke('update', ''); + }); + + teardown(function () { + // hack to cleanup responders + Ajax.Responders.responders = [Ajax.Responders.responders[0]]; + }); + + test("synchronous request", function () { + assert.equal("", $('content').innerHTML); + assert.equal(0, Ajax.activeRequestCount); + + new Ajax.Request('/fixtures/hello.js', { + asynchronous: false, + method: 'GET', + evalJS: 'force' + }); + + assert.equal(0, Ajax.activeRequestCount); + }); + + test("asynchronous request", function (done) { + assert.equal("", $("content").innerHTML); + + new Ajax.Request('/fixtures/hello.js', { + asynchronous: true, + method: 'GET', + evalJS: 'force' + }); + + setTimeout(function () { + var h2 = $('content').firstChild; + assert.equal("Hello world!", h2.innerHTML); + done(); + }, 1000); + }); + + suite('Updater', function () { + + setup(function () { + $('content', 'content2').invoke('update', ''); + }); + + test('basic', function (done) { + assert.equal("", $('content').innerHTML); + + new Ajax.Updater('content', '/fixtures/content.html', { method: 'get' }); + + setTimeout(function () { + assertContent('content', sentence, 'simple updater'); + + $('content').update(''); + assert.equal("", $('content').innerHTML); + + new Ajax.Updater( + { success: 'content', failure: 'content2' }, + '/fixtures/content.html', + { method: 'get', parameters: { pet: 'monkey' } } + ); + + setTimeout(function () { + assertContent('content', sentence, 'success/failure updater'); + assertContent('content2', '', 'failure DIV should be empty'); + done(); + }, 1000); + + }, 1000); + + }); + + test('with insertion', function (done) { + $('content').update(); + new Ajax.Updater('content', '/fixtures/content.html', { + method: 'get', + insertion: Insertion.Top + }); + assertContent('content', ''); + + setTimeout(function() { + assertContent('content', sentence, 'Insertion.Top'); + $('content').update(); + + new Ajax.Updater('content', '/fixtures/content.html', { + method: 'get', + insertion: 'bottom' + }); + + setTimeout(function () { + assertContent('content', sentence, 'bottom insertion'); + $('content').update(); + + new Ajax.Updater('content', '/fixtures/content.html', { + method: 'get', + insertion: 'after' + }); + + setTimeout(function () { + assert.equal( + 'five dozen', + $('content').next().innerHTML.strip().toLowerCase(), + 'after insertion' + ); + done(); + }, 1000); + + }, 1000); + + }, 1000); + + }); + + test('with options', function () { + var options = { + method: 'get', + asynchronous: false, + evalJS: 'force', + onComplete: Prototype.emptyFunction + }; + + var request = new Ajax.Updater('content', '/fixtures/hello.js', options); + request.options.onComplete = Prototype.emptyFunction; + assert.strictEqual(Prototype.emptyFunction, options.onComplete); + }); + + }); // Updater + + test('responders', function (done) { + var r = Ajax.Responders.responders; + assert.equal(1, r.length); + + var dummyResponder = { + onComplete: Prototype.emptyFunction + }; + + Ajax.Responders.register(dummyResponder); + assert.equal(2, r.length); + + // Don't add twice. + Ajax.Responders.register(dummyResponder); + assert.equal(2, r.length, 'what'); + + Ajax.Responders.unregister(dummyResponder); + assert.equal(1, Ajax.Responders.responders.length); + + var responder = { + onCreate: function(req) { responderCounter++; }, + onLoading: function(req) { responderCounter++; }, + onComplete: function(req) { responderCounter++; } + }; + Ajax.Responders.register(responder); + + assert.equal(0, responderCounter); + assert.equal(0, Ajax.activeRequestCount); + + new Ajax.Request('/fixtures/content.html', { + method: 'get', + parameters: 'pet=monkey' + }); + + assert.equal(1, responderCounter); + assert.equal(1, Ajax.activeRequestCount); + + setTimeout(function () { + assert.equal(3, responderCounter); + assert.equal(0, Ajax.activeRequestCount); + done(); + }, 1000); + + }); + + test('eval response should be called before onComplete', function () { + assert.equal('', $('content').innerHTML); + assert.equal(0, Ajax.activeRequestCount); + + new Ajax.Request('/fixtures/hello.js', extendDefault({ + onComplete: function(response) { + assert.notEqual('', $('content').innerHTML); + } + })); + assert.equal(0, Ajax.activeRequestCount); + + var h2 = $('content').firstChild; + assert.equal('Hello world!', h2.innerHTML); + }); + + test('Content-Type set for simulated verbs', function () { + new Ajax.Request('/inspect', extendDefault({ + method: 'put', + contentType: 'application/bogus', + onComplete: function (response) { + assert.equal( + 'application/bogus; charset=UTF-8', + response.responseJSON.headers['content-type'] + ); + } + })); + }); + + test('onCreate callback', function () { + new Ajax.Request('/fixtures/content.html', extendDefault({ + onCreate: function (transport) { + assert.equal(0, transport.readyState); + }, + onComplete: function (transport) { + assert.notEqual(0, transport.readyState); + } + })); + }); + + test('evalJS', function () { + $('content').update(); + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.js, + onComplete: function (transport) { + var h2 = $('content').firstChild; + assert.equal('Hello world!', h2.innerHTML); + } + })); + + $('content').update(); + new Ajax.Request('/response', extendDefault({ + evalJS: false, + parameters: Fixtures.Ajax.js, + onComplete: function () { + assert.equal('', $('content').innerHTML); + } + })); + + $('content').update(); + new Ajax.Request("/fixtures/hello.js", extendDefault({ + evalJS: 'force', + onComplete: function(transport) { + var h2 = $('content').firstChild; + assert.equal('Hello world!', h2.innerHTML); + } + })); + + }); + + test('callbacks', function () { + var options = extendDefault({ + onCreate: function (transport) { + assert.isInstanceOf(transport, Ajax.Response); + } + }); + + Ajax.Request.Events.each(function (state) { + options['on' + state] = options.onCreate; + }); + + new Ajax.Request('/fixtures/content.html', options); + }); + + test('response text', function () { + new Ajax.Request('/fixtures/empty.html', extendDefault({ + onComplete: function (transport) { + assert.equal('', transport.responseText); + } + })); + + new Ajax.Request('/fixtures/content.html', extendDefault({ + onComplete: function (transport) { + assert.equal(sentence, transport.responseText.toLowerCase()); + } + })); + }); + + test('responseXML', function () { + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.xml, + onComplete: function (transport) { + assert.equal( + 'foo', + transport.responseXML.getElementsByTagName('name')[0].getAttribute('attr') + ); + } + })); + }); + + test('responseJSON', function () { + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.json, + onComplete: function (transport) { + assert.equal(123, transport.responseJSON.test); + } + })); + + new Ajax.Request('/response', extendDefault({ + parameters: { + 'Content-Length': 0, + 'Content-Type': 'application/json' + }, + onComplete: function (transport) { + assert.isNull(transport.responseJSON); + } + })); + + new Ajax.Request('/response', extendDefault({ + evalJSON: false, + parameters: Fixtures.Ajax.json, + onComplete: function (transport) { + assert.isNull(transport.responseJSON); + } + })); + + new Ajax.Request('/response', extendDefault({ + sanitizeJSON: true, + parameters: Fixtures.Ajax.invalidJson, + onException: function (request, error) { + assert.equal('SyntaxError', error.name); + } + })); + + new Ajax.Request('/fixtures/data.json', extendDefault({ + evalJSON: 'force', + onComplete: function (transport) { + assert.equal(123, transport.responseJSON.test); + } + })); + }); + + test('headerJSON', function () { + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.headerJson, + onComplete: function (transport, json) { + assert.equal('hello #éà', transport.headerJSON.test); + assert.equal('hello #éà', json.test); + } + })); + + new Ajax.Request('/response', extendDefault({ + onComplete: function (transport, json) { + assert.isNull(transport.headerJSON); + assert.isNull(json); + } + })); + }); + + test('getHeader', function () { + new Ajax.Request('/response', extendDefault({ + parameters: { 'X-TEST': 'some value' }, + onComplete: function (transport) { + assert.equal('some value', transport.getHeader('X-Test')); + assert.isNull(transport.getHeader('X-Non-Existent')); + } + })); + }); + + test('parameters can be a hash', function () { + new Ajax.Request('/response', extendDefault({ + parameters: $H({ one: "two", three: "four" }), + onComplete: function (transport) { + assert.equal('two', transport.getHeader('one')); + assert.equal('four', transport.getHeader('three')); + assert.isNull(transport.getHeader('toObject')); + } + })); + }); + + test('parameters string order is preserved', function () { + new Ajax.Request('/inspect', extendDefault({ + parameters: "cool=1&bad=2&cool=3&bad=4", + method: 'post', + onComplete: function (transport) { + var bodyWithoutWart = + transport.responseJSON.body.match(/((?:(?!&_=$).)*)/)[1]; + assert.equal('cool=1&bad=2&cool=3&bad=4', bodyWithoutWart); + } + })); + }); + + test('isSameOrigin', function () { + var isSameOrigin = Ajax.Request.prototype.isSameOrigin; + + assert(isSameOrigin.call({ url: '/foo/bar.html' }), + '/foo/bar.html should be same-origin'); + assert(isSameOrigin.call({ url: window.location.toString() }), + 'current window location should be same-origin'); + assert(!isSameOrigin.call({ url: 'http://example.com' }), + 'example.com should not be same-origin'); + + Ajax.Request.prototype.isSameOrigin = function () { + return false; + }; + + $('content').update('same origin policy'); + + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.js, + onComplete: function (transport) { + assert.equal('same origin policy', $('content').innerHTML); + } + })); + + new Ajax.Request('/response', extendDefault({ + parameters: Fixtures.Ajax.invalidJson, + onException: function (request, error) { + assert.equal('SyntaxError', error.name); + } + })); + + new Ajax.Request('/response', extendDefault({ + parameters: { 'X-JSON': '{});window.attacked = true;({}' }, + onException: function (request, error) { + assert.equal('SyntaxError', error.name); + } + })); + + Ajax.Request.prototype.isSameOrigin = isSameOrigin; + }); + +}); + diff --git a/test.new/tests/array.old.test.js b/test.new/tests/array.old.test.js new file mode 100644 index 000000000..5da6be7df --- /dev/null +++ b/test.new/tests/array.old.test.js @@ -0,0 +1,404 @@ +var globalArgsTest = 'nothing to see here'; + +suite('Array', function() { + this.timeout(0); + this.name = 'array'; + + + test('$A({}) should equal []', function() { + assert.deepEqual([], $A({}), "$A({}) != []") + }); + + test('use $A() on function arguments', function() { + function toArrayOnArguments() { + globalArgsTest = $A(arguments); + } + + toArrayOnArguments(); + assert.deepEqual([], globalArgsTest, "globalArgsTest != []"); + + toArrayOnArguments('foo'); + assert.deepEqual(['foo'], globalArgsTest, "globalArgsTest != ['foo']"); + + toArrayOnArguments('foo', 'bar'); + assert.deepEqual(['foo', 'bar'], globalArgsTest, + "globalArgsTest != ['foo', 'bar']"); + }); + + test('use $A() On NodeList', function() { + // direct NodeList + assert( + 0 === $A($('testfixture').childNodes).length, + 'HTML childNodes length != 0' + ); + + // DOM + var element = document.createElement('div'); + element.appendChild(document.createTextNode('22')); + (2).times(function () { + element.appendChild(document.createElement('span')); + }); + assert( + 3 === $A(element.childNodes).length, + 'DOM childNodes length != 3' + ); + + // HTML String + element = document.createElement('div'); + $(element).update('22 2; + })); + assert(![1, 2, 3, 4, 5].any(function(value) { + return value > 5; + })); + + var x = [1, 2, 3], traversed = []; + delete x[1]; + x.any(function(val) { traversed.push(val); }); + assert.deepEqual([1, 3], traversed); + assert(2 === traversed.length); + }); + test(".all() method", function() { + assert([].all()); + + assert([true, true, true].all()); + assert(![true, false, false].all()); + assert(![false, false, false].all()); + + assert([1, 2, 3, 4, 5].all(function(value) { + return value > 0; + })); + assert(![1, 2, 3, 4, 5].all(function(value) { + return value > 1; + })); + + var x = [1, 2, 3], traversed = []; + delete x[1]; + x.all(function(val) { traversed.push(val); return true; }); + assert.deepEqual([1, 3], traversed); + assert(2, traversed.length); + }); + }); + + test("$w()", function() { + assert.deepEqual(['a', 'b', 'c', 'd'], $w('a b c d')); + assert.deepEqual([], $w(' ')); + assert.deepEqual([], $w('')); + assert.deepEqual([], $w(null)); + assert.deepEqual([], $w(undefined)); + assert.deepEqual([], $w()); + assert.deepEqual([], $w(10)); + assert.deepEqual(['a'], $w('a')); + assert.deepEqual(['a'], $w('a ')); + assert.deepEqual(['a'], $w(' a')); + assert.deepEqual(['a', 'b', 'c', 'd'], $w(' a b\nc\t\nd\n')); + }); + + + test(".each() On Sparse Arrays", function() { + var counter = 0; + + var sparseArray = [0, 1]; + sparseArray[5] = 5; + sparseArray.each( function(item) { counter++; }); + + assert(3 === counter, "Array#each should skip nonexistent keys in an array"); + }); + + test(".map() Generic", function() { + var result = Array.prototype.map.call({0:0, 1:1, length:2}); + assert.deepEqual([0, 1], result); + }); + + + test(".findAll() Generic", function() { + var result = Array.prototype.findAll.call({0:0, 1:1, length:2}, function(x) { + return x === 1; + }); + assert.deepEqual([1], result); + }); + + + test(".any() Generic", function() { + assert(Array.prototype.any.call({ 0:false, 1:true, length:2 })); + assert(!Array.prototype.any.call({ 0:false, 1:false, length:2 })); + }); + + + test(".all() Generic", function() { + assert(Array.prototype.all.call({ 0:true, 1:true, length:2 })); + assert(!Array.prototype.all.call({ 0:false, 1:true, length:2 })); + }); + +}); \ No newline at end of file diff --git a/test.new/tests/array.test.js b/test.new/tests/array.test.js new file mode 100644 index 000000000..f896eed8f --- /dev/null +++ b/test.new/tests/array.test.js @@ -0,0 +1,359 @@ +var globalArgsTest = 'nothing to see here'; + +suite('Array', function () { + this.name = 'array'; + + test('$A', function () { + assert.enumEqual([], $A({})); + }); + + test('$A (on arguments)', function () { + function toArrayOnArguments(){ + globalArgsTest = $A(arguments); + } + toArrayOnArguments(); + assert.enumEqual([], globalArgsTest); + toArrayOnArguments('foo'); + assert.enumEqual(['foo'], globalArgsTest); + toArrayOnArguments('foo','bar'); + assert.enumEqual(['foo','bar'], globalArgsTest); + }); + + test('$A (on NodeList)', function () { + // direct HTML + assert.equal(3, $A($('test_node').childNodes).length); + + // DOM + var element = document.createElement('div'); + element.appendChild(document.createTextNode('22')); + (2).times(function(){ element.appendChild(document.createElement('span')); }); + assert.equal(3, $A(element.childNodes).length); + + // HTML String + element = document.createElement('div'); + $(element).update('22 2; + })); + assert(![1,2,3,4,5].any(function(value) { + return value > 5; + })); + + var x = [1,2,3], traversed = []; + delete x[1]; + x.any(function(val) { traversed.push(val); }); + assert.enumEqual([1, 3], traversed); + assert.strictEqual(2, traversed.length); + }); + + test('#any (used as generic)', function () { + assert(Array.prototype.any.call({ 0:false, 1:true, length:2 })); + assert(!Array.prototype.any.call({ 0:false, 1:false, length:2 })); + }); + + test('#all', function () { + assert([].all()); + + assert([true, true, true].all()); + assert(![true, false, false].all()); + assert(![false, false, false].all()); + + assert([1,2,3,4,5].all(function(value) { + return value > 0; + })); + assert(![1,2,3,4,5].all(function(value) { + return value > 1; + })); + + var x = [1,2,3], traversed = []; + delete x[1]; + x.all(function(val) { traversed.push(val); return true; }); + assert.enumEqual([1, 3], traversed); + assert.strictEqual(2, traversed.length); + }); + + test('#all (used as generic)', function () { + assert(Array.prototype.all.call({ 0:true, 1:true, length:2 })); + assert(!Array.prototype.all.call({ 0:false, 1:true, length:2 })); + }); + +}); diff --git a/test.new/tests/base.test.js b/test.new/tests/base.test.js new file mode 100644 index 000000000..3026d5c89 --- /dev/null +++ b/test.new/tests/base.test.js @@ -0,0 +1,52 @@ + +suite('Base', function () { + + this.name = 'base'; + + test('Browser detection', function () { + + var results = $H(Prototype.Browser).map(function (engine) { + return engine; + }).partition(function (engine) { + return engine[1] === true; + }); + var trues = results[0], falses = results[1]; + + info('User agent string is: ' + navigator.userAgent); + + assert(trues.size() === 0 || trues.size() === 1, + 'There should be only one or no browser detected.'); + + // we should have definite trues or falses here + trues.each(function(result) { + assert(result[1] === true); + }, this); + falses.each(function(result) { + assert(result[1] === false); + }, this); + + var ua = navigator.userAgent; + + if (ua.indexOf('AppleWebKit/') > -1) { + info('Running on WebKit'); + assert(Prototype.Browser.WebKit); + } + + if (Object.prototype.toString.call(window.opera) === '[object Opera]') { + info('Running on Opera'); + assert(Prototype.Browser.Opera); + } + + if (ua.indexOf('MSIE') > -1) { + info('Running on IE'); + assert(Prototype.Browser.IE); + } + + if (ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1) { + info('Running on Gecko'); + assert(Prototype.Browser.Gecko); + } + + }); + +}); \ No newline at end of file diff --git a/test.new/tests/class.test.js b/test.new/tests/class.test.js new file mode 100644 index 000000000..bb41a468a --- /dev/null +++ b/test.new/tests/class.test.js @@ -0,0 +1,223 @@ +// base class +var Animal = Class.create({ + initialize: function(name) { + this.name = name; + }, + name: "", + eat: function() { + return this.say("Yum!"); + }, + say: function(message) { + return this.name + ": " + message; + } +}); + +// subclass that augments a method +var Cat = Class.create(Animal, { + eat: function($super, food) { + if (food instanceof Mouse) return $super(); + else return this.say("Yuk! I only eat mice."); + } +}); + +// empty subclass +var Mouse = Class.create(Animal, {}); + +//mixins +var Sellable = { + getValue: function(pricePerKilo) { + return this.weight * pricePerKilo; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}; + +var Reproduceable = { + reproduce: function(partner) { + if (partner.constructor != this.constructor || partner.sex == this.sex) + return null; + var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female'; + return new this.constructor('baby', weight, sex); + } +}; + +// base class with mixin +var Plant = Class.create(Sellable, { + initialize: function(name, weight) { + this.name = name; + this.weight = weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}); + +// subclass with mixin +var Dog = Class.create(Animal, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + } +}); + +// subclass with mixins +var Ox = Class.create(Animal, Sellable, Reproduceable, { + initialize: function($super, name, weight, sex) { + this.weight = weight; + this.sex = sex; + $super(name); + }, + + eat: function(food) { + if (food instanceof Plant) + this.weight += food.weight; + }, + + inspect: function() { + return '#'.interpolate(this); + } +}); + + + + +suite("Class", function () { + this.name = 'class'; + + test('create', function () { + assert(Object.isFunction(Animal), 'Animal is not a constructor'); + }); + + test('instantiation', function () { + var pet = new Animal('Nibbles'); + assert.equal('Nibbles', pet.name, 'property not initialized'); + assert.equal('Nibbles: Hi!', pet.say('Hi!')); + assert.equal(Animal, pet.constructor, 'bad constructor reference'); + assert.isUndefined(pet.superclass); + + var Empty = Class.create(); + assert.equal('object', typeof new Empty); + }); + + test('inheritance', function () { + var tom = new Cat('Tom'); + assert.equal(Cat, tom.constructor, 'bad constructor reference'); + assert.equal(Animal, tom.constructor.superclass, 'bad superclass reference'); + assert.equal('Tom', tom.name); + assert.equal('Tom: meow', tom.say('meow')); + assert.equal('Tom: Yuk! I only eat mice.', tom.eat(new Animal)); + }); + + test('superclass method call', function () { + var tom = new Cat('Tom'); + assert.equal('Tom: Yum!', tom.eat(new Mouse)); + + var Dodo = Class.create(Animal, { + initialize: function ($super, name) { + $super(name); + this.extinct = true; + }, + + say: function ($super, message) { + return $super(message) + " honk honk"; + } + }); + + var gonzo = new Dodo('Gonzo'); + assert.equal('Gonzo', gonzo.name); + assert(gonzo.extinct, 'Dodo birds should be extinct'); + assert.equal('Gonzo: hello honk honk', gonzo.say('hello')); + }); + + test('addMethods', function () { + var tom = new Cat('Tom'); + var jerry = new Mouse('Jerry'); + + Animal.addMethods({ + sleep: function () { + return this.say('ZZZ'); + } + }); + + Mouse.addMethods({ + sleep: function ($super) { + return $super() + " ... no, can't sleep! Gotta steal cheese!"; + }, + + escape: function (cat) { + return this.say('(from a mousehole) Take that, ' + cat.name + '!'); + } + }); + + assert.equal('Tom: ZZZ', tom.sleep(), + 'added instance method not available to subclass'); + assert.equal("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", + jerry.sleep()); + assert.equal("Jerry: (from a mousehole) Take that, Tom!", + jerry.escape(tom)); + + // Ensure that a method has not propagated _up_ the prototype chain. + assert.isUndefined(tom.escape); + assert.isUndefined(new Animal().escape); + + Animal.addMethods({ + sleep: function () { + return this.say('zZzZ'); + } + }); + + assert.equal("Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!", + jerry.sleep()); + + }); + + test('base class with mixin', function () { + var grass = new Plant('grass', 3); + assert.respondsTo('getValue', grass); + assert.equal('#', grass.inspect()); + }); + + test('subclass with mixin', function () { + var snoopy = new Dog('Snoopy', 12, 'male'); + assert.respondsTo('reproduce', snoopy); + }); + + test('subclass with mixins', function () { + var cow = new Ox('cow', 400, 'female'); + assert.equal('#', cow.inspect()); + assert.respondsTo('reproduce', cow); + assert.respondsTo('getValue', cow); + }); + + test('class with toString and valueOf methods', function () { + var Foo = Class.create({ + toString: function() { return "toString"; }, + valueOf: function() { return "valueOf"; } + }); + + var Bar = Class.create(Foo, { + valueOf: function() { return "myValueOf"; } + }); + + var Parent = Class.create({ + m1: function(){ return 'm1'; }, + m2: function(){ return 'm2'; } + }); + var Child = Class.create(Parent, { + m1: function($super) { return 'm1 child'; }, + m2: function($super) { return 'm2 child'; } + }); + + assert(new Child().m1.toString().indexOf('m1 child') > -1); + + assert.equal("toString", new Foo().toString()); + assert.equal("valueOf", new Foo().valueOf() ); + assert.equal("toString", new Bar().toString()); + assert.equal("myValueOf", new Bar().valueOf() ); + }); + +}); \ No newline at end of file diff --git a/test.new/tests/date.test.js b/test.new/tests/date.test.js new file mode 100644 index 000000000..8a13c3c80 --- /dev/null +++ b/test.new/tests/date.test.js @@ -0,0 +1,18 @@ +suite('Date', function () { + this.name = 'date'; + + test('#toJSON', function () { + assert.match( + new Date(Date.UTC(1970, 0, 1)).toJSON(), + /^1970-01-01T00:00:00(\.000)?Z$/ + ); + }); + + test('#toISOString', function () { + assert.match( + new Date(Date.UTC(1970, 0, 1)).toISOString(), + /^1970-01-01T00:00:00(\.000)?Z$/ + ); + }); + +}); \ No newline at end of file diff --git a/test.new/tests/dom.test.js b/test.new/tests/dom.test.js new file mode 100644 index 000000000..6a6007e9e --- /dev/null +++ b/test.new/tests/dom.test.js @@ -0,0 +1,1615 @@ +var testVar = 'to be updated', testVar2 = ''; + +Element.addMethods({ + hashBrowns: function(element) { return 'hash browns'; } +}); + +Element.addMethods("LI", { + pancakes: function(element) { return "pancakes"; } +}); + +Element.addMethods("DIV", { + waffles: function(element) { return "waffles"; } +}); + +Element.addMethods($w("li div"), { + orangeJuice: function(element) { return "orange juice"; } +}); + + +function getInnerHTML (id) { + return $(id).innerHTML.toString().toLowerCase().gsub(/[\r\n\t]/, ''); +} + +function createParagraph (text) { + var p = document.createElement('p'); + p.appendChild(document.createTextNode(text)); + return p; +} + +var RESIZE_DISABLED = false; + +function simulateClick(node) { + var oEvent; + if (document.createEvent) { + oEvent = document.createEvent('MouseEvents'); + oEvent.initMouseEvent('click', true, true, document.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, node); + node.dispatchEvent(oEvent); + } else { + node.click(); + } +} + +var documentViewportProperties = null; + + +suite('DOM', function () { + this.name = 'dom'; + + setup(function () { + + if (documentViewportProperties) return; + + // Based on properties check from http://www.quirksmode.org/viewport/compatibility.html. + + documentViewportProperties = { + properties: [ + 'self.pageXOffset', 'self.pageYOffset', + 'self.screenX', 'self.screenY', + 'self.innerHeight', 'self.innerWidth', + 'self.outerHeight', 'self.outerWidth', + 'self.screen.height', 'self.screen.width', + 'self.screen.availHeight', 'self.screen.availWidth', + 'self.screen.availTop', 'self.screen.availLeft', + 'self.screen.Top', 'self.screen.Left', + 'self.screenTop', 'self.screenLeft', + 'document.body.clientHeight', 'document.body.clientWidth', + 'document.body.scrollHeight', 'document.body.scrollWidth', + 'document.body.scrollLeft', 'document.body.scrollTop', + 'document.body.offsetHeight', 'document.body.offsetWidth', + 'document.body.offsetTop', 'document.body.offsetLeft' + ].inject([], function (properties, prop) { + if (!self.screen && prop.include('self.screen')) return; + if (!document.body && prop.include('document.body')) return; + + properties.push(prop); + + if (prop.include('body') && document.documentElement) { + properties.push(prop.sub('.body', '.documentElement')); + } + + return properties; + }), + + inspect: function () { + var props = []; + this.properties.each(function (prop) { + if (eval(prop)) props[prop] = eval(prop); + }, this); + return props; + } + }; + }); + + + + test('$', function () { + assert.isUndefined($(), '$() should be undefined'); + + assert.isNull(document.getElementById('noWayThisIDExists'), + 'nonexistent ID should return null from getElementById'); + + assert.isNull($('noWayThisIDExists'), + 'nonexistent ID should return null from $'); + + assert.strictEqual(document.getElementById('testdiv'), $('testdiv'), + 'getElementById and $ should return the same element'); + + assert.deepEqual( + [ $('testdiv'), $('container') ], + $('testdiv', 'container') + ); + + assert.deepEqual( + [ $('testdiv'), null, $('container') ], + $('testdiv', 'noWayThisIDExists', 'container') + ); + + var elt = $('testdiv'); + + assert.strictEqual(elt, $(elt)); + assert.respondsTo('hide', elt); + assert.respondsTo('childOf', elt); + }); + + + test('getElementsByClassName', function () { + + if (document.getElementsByClassName.toString().include('[native code]')) { + console.log("browser uses native getElementsByClassName; skipping tests"); + return; + } + + var div = $('class_names'), list = $('class_names_ul'); + + assert.elementsMatch( + document.getElementsByClassName('A'), + 'p.A', 'ul#class_names_ul.A', 'li.A.C' + ); + + var isElementPrototypeSupported = (function(){ + var el = document.createElement('div'); + var result = typeof el.show != 'undefined'; + el = null; + return result; + })(); + + if (!isElementPrototypeSupported) { + assert.isUndefined(document.getElementById('unextended').show); + } + + assert.elementsMatch(div.getElementsByClassName('B'), 'ul#class_names_ul.A.B', 'div.B.C.D'); + assert.elementsMatch(div.getElementsByClassName('D C B'), 'div.B.C.D'); + assert.elementsMatch(div.getElementsByClassName(' D\nC\tB '), 'div.B.C.D'); + assert.elementsMatch(div.getElementsByClassName($w('D C B'))); + assert.elementsMatch(list.getElementsByClassName('A'), 'li.A.C'); + assert.elementsMatch(list.getElementsByClassName(' A '), 'li.A.C'); + assert.elementsMatch(list.getElementsByClassName('C A'), 'li.A.C'); + assert.elementsMatch(list.getElementsByClassName("C\nA "), 'li.A.C'); + assert.elementsMatch(list.getElementsByClassName('B')); + assert.elementsMatch(list.getElementsByClassName('1'), 'li.1'); + assert.elementsMatch(list.getElementsByClassName([1]), 'li.1'); + assert.elementsMatch(list.getElementsByClassName(['1 junk'])); + assert.elementsMatch(list.getElementsByClassName('')); + assert.elementsMatch(list.getElementsByClassName(' ')); + assert.elementsMatch(list.getElementsByClassName([''])); + assert.elementsMatch(list.getElementsByClassName([' ', ''])); + assert.elementsMatch(list.getElementsByClassName({})); + + // those lookups shouldn't have extended all nodes in document + if (!isElementPrototypeSupported) { + assert.isUndefined(document.getElementById('unextended')['show']); + } + + }); + + test('.insert (with HTML)', function () { + + Element.insert('insertions-main', { + before:'

    before text

    more testing

    ' + }); + assert(getInnerHTML('insertions-container').startsWith('

    before text

    more testing

    ')); + + Element.insert('insertions-main', { + after:'

    after text

    more testing

    ' + }); + assert(getInnerHTML('insertions-container').endsWith('

    after text

    more testing

    ')); + + Element.insert('insertions-main', { + top:'

    top text.

    more testing

    ' + }); + assert(getInnerHTML('insertions-main').startsWith('

    top text.

    more testing

    ')); + + Element.insert('insertions-main', { + bottom:'

    bottom text.

    more testing

    ' + }); + assert(getInnerHTML('insertions-main').endsWith('

    bottom text.

    more testing

    ')); + + }); + + + test('.insert (with DOM node)', function () { + Element.insert('insertions-node-main', { + before: createParagraph('node before') + }); + assert(getInnerHTML('insertions-node-container').startsWith('

    node before

    ')); + + Element.insert('insertions-node-main', { + after: createParagraph('node after') + }); + assert(getInnerHTML('insertions-node-container').endsWith('

    node after

    ')); + + Element.insert('insertions-node-main', { + top: createParagraph('node top') + }); + assert(getInnerHTML('insertions-node-main').startsWith('

    node top

    ')); + + Element.insert('insertions-node-main', { + bottom: createParagraph('node bottom')} + ); + assert(getInnerHTML('insertions-node-main').endsWith('

    node bottom

    ')); + + assert.equal( + $('insertions-node-main'), + $('insertions-node-main').insert(document.createElement('p')), + 'insert should return the original node' + ); + }); + + + test('.insert (with toElement method)', function () { + Element.insert('insertions-node-main', { + toElement: createParagraph.curry('toElement') + }); + assert(getInnerHTML('insertions-node-main').endsWith('

    toelement

    ')); + + Element.insert('insertions-node-main', { + bottom: { toElement: createParagraph.curry('bottom toElement') } + }); + assert(getInnerHTML('insertions-node-main').endsWith('

    bottom toelement

    ')); + }); + + + test('.insert (with toHTML method)', function () { + Element.insert('insertions-node-main', { + toHTML: function() { return '

    toHTML

    '; } + }); + assert(getInnerHTML('insertions-node-main').endsWith('

    tohtml

    ')); + + Element.insert('insertions-node-main', { + bottom: { + toHTML: function() { return '

    bottom toHTML

    '; } + } + }); + assert(getInnerHTML('insertions-node-main').endsWith('

    bottom tohtml

    ')); + }); + + test('.insert (with non-string)', function () { + Element.insert('insertions-main', { bottom: 3 }); + assert(getInnerHTML('insertions-main').endsWith('3')); + }); + + test('.insert (in tables)', function () { + Element.insert('second_row', { + after:'Third Row' + }); + assert($('second_row').parentNode == $('table'), + 'table rows should be inserted correctly'); + + $('a_cell').insert({ top: 'hello world' }); + assert($('a_cell').innerHTML.startsWith('hello world'), + 'content should be inserted into table cells correctly'); + + $('a_cell').insert({ after: 'hi planet' }); + assert.equal('hi planet', $('a_cell').next().innerHTML, + 'table cells should be inserted after existing table cells correctly'); + + $('table_for_insertions').insert('a cell!'); + assert($('table_for_insertions').innerHTML.gsub('\r\n', '').toLowerCase().include('a cell!'), + 'complex content should be inserted into a table correctly'); + + $('row_1').insert({ after:'last' }); + assert.equal('last', $A($('table_for_row_insertions').getElementsByTagName('tr')).last().lastChild.innerHTML, + 'complex content should be inserted after a table row correctly'); + }); + + test('.insert (in select)', function () { + var selectTop = $('select_for_insert_top'); + var selectBottom = $('select_for_insert_bottom'); + + selectBottom.insert(''); + assert.equal('option 45', selectBottom.getValue()); + selectTop.insert({top:''}); + assert.equal(4, selectTop.options.length); + }); + + test('#insert', function () { + $('element-insertions-main').insert({before:'some text before'}); + assert(getInnerHTML('element-insertions-container').startsWith('some text before'), 'some text before'); + $('element-insertions-main').insert({after:'some text after'}); + assert(getInnerHTML('element-insertions-container').endsWith('some text after'), 'some text after'); + $('element-insertions-main').insert({top:'some text top'}); + assert(getInnerHTML('element-insertions-main').startsWith('some text top'), 'some text top'); + $('element-insertions-main').insert({bottom:'some text bottom'}); + assert(getInnerHTML('element-insertions-main').endsWith('some text bottom'), 'some text bottom'); + + $('element-insertions-main').insert('some more text at the bottom'); + assert(getInnerHTML('element-insertions-main').endsWith('some more text at the bottom'), + 'some more text at the bottom'); + + $('element-insertions-main').insert({TOP:'some text uppercase top'}); + assert(getInnerHTML('element-insertions-main').startsWith('some text uppercase top'), 'some text uppercase top'); + + $('element-insertions-multiple-main').insert({ + top:'1', bottom:2, before: new Element('p').update('3'), after:'4' + }); + assert(getInnerHTML('element-insertions-multiple-main').startsWith('1'), '1'); + assert(getInnerHTML('element-insertions-multiple-main').endsWith('2'), '2'); + assert(getInnerHTML('element-insertions-multiple-container').startsWith( + '

    3

    '), '

    3

    '); + assert(getInnerHTML('element-insertions-multiple-container').endsWith('4'), '4'); + + $('element-insertions-main').update('test'); + $('element-insertions-main').insert(null); + $('element-insertions-main').insert({bottom:null}); + assert.equal('test', getInnerHTML('element-insertions-main'), 'should insert nothing when called with null'); + $('element-insertions-main').insert(1337); + assert.equal('test1337', getInnerHTML('element-insertions-main'), 'should coerce to string when called with number'); + }); + + + test('#insert (with new Element)', function () { + var container = new Element('div'), element = new Element('div'); + container.insert(element); + + element.insert({ before: '

    a paragraph

    ' }); + assert.equal('

    a paragraph

    ', getInnerHTML(container)); + element.insert({ after: 'some text' }); + assert.equal('

    a paragraph

    some text', getInnerHTML(container)); + + element.insert({ top: '

    a paragraph

    ' }); + assert.equal('

    a paragraph

    ', getInnerHTML(element)); + element.insert('some text'); + assert.equal('

    a paragraph

    some text', getInnerHTML(element)); + + }); + + + test('Insertion (backwards-compatibility)', function () { + new Insertion.Before('element-insertions-main', 'some backward-compatibility testing before'); + assert(getInnerHTML('element-insertions-container').include('some backward-compatibility testing before')); + new Insertion.After('element-insertions-main', 'some backward-compatibility testing after'); + assert(getInnerHTML('element-insertions-container').include('some backward-compatibility testing after')); + new Insertion.Top('element-insertions-main', 'some backward-compatibility testing top'); + assert(getInnerHTML('element-insertions-main').startsWith('some backward-compatibility testing top')); + new Insertion.Bottom('element-insertions-main', 'some backward-compatibility testing bottom'); + assert(getInnerHTML('element-insertions-main').endsWith('some backward-compatibility testing bottom')); + }); + + test('#wrap', function () { + var element = $('wrap'), parent = document.createElement('div'); + element.wrap(); + assert(getInnerHTML('wrap-container').startsWith('
    \ntestVar="hello!";\n'); + assert.equal('hello from div!', $('testdiv').innerHTML); + + wait(100, function () { + assert.equal('hello!', testVar); + + Element.update('testdiv','another hello from div!\n" + + str.evalScripts.bind(str).defer(); + + wait(50, function() { + assert(window.deferBoundProperlyOnString); + }); + }); + }); + }); + }); + + test('#methodize', function () { + var Foo = { bar: function(baz) { return baz; } }; + var baz = { quux: Foo.bar.methodize() }; + + assert.equal(Foo.bar.methodize(), baz.quux); + assert.equal(baz, Foo.bar(baz)); + assert.equal(baz, baz.quux()); + }); + + test('#bindAsEventListener', function () { + for (var i = 0; i < 10; ++i){ + var div = document.createElement('div'); + div.setAttribute('id','test-'+i); + document.body.appendChild(div); + var tobj = new TestObj(); + var eventTest = { test: true }; + var call = tobj.assertingEventHandler.bindAsEventListener(tobj, + assert.equal.bind(assert, eventTest), + assert.equal.bind(assert, arg1), + assert.equal.bind(assert, arg2), + assert.equal.bind(assert, arg3), arg1, arg2, arg3 ); + call(eventTest); + } + }); + +}); diff --git a/test.new/tests/hash.test.js b/test.new/tests/hash.test.js new file mode 100644 index 000000000..437baedd6 --- /dev/null +++ b/test.new/tests/hash.test.js @@ -0,0 +1,240 @@ +Fixtures.Hash = { + one: { a: 'A#' }, + + many: { + a: 'A', + b: 'B', + c: 'C', + d: 'D#' + }, + + functions: { + quad: function(n) { return n*n; }, + plus: function(n) { return n+n; } + }, + + multiple: { color: $w('r g b') }, + multiple_nil: { color: ['r', null, 'g', undefined, 0] }, + multiple_all_nil: { color: [null, undefined] }, + multiple_empty: { color: [] }, + multiple_special: { 'stuff[]': $w('$ a ;') }, + + value_undefined: { a:"b", c:undefined }, + value_null: { a:"b", c:null }, + value_zero: { a:"b", c:0 } +}; + + +/// + + +suite('Hash', function () { + this.name = 'hash'; + + test('#set', function () { + var h = $H({a: 'A'}); + + assert.equal('B', h.set('b', 'B')); + assert.hashEqual({a: 'A', b: 'B'}, h); + + assert.isUndefined(h.set('c')); + assert.hashEqual({a: 'A', b: 'B', c: undefined}, h); + }); + + test('#get', function () { + var h = $H({a: 'A'}); + assert.equal('A', h.get('a')); + assert.isUndefined(h.a); + assert.isUndefined($H({}).get('a')); + + assert.isUndefined($H({}).get('toString')); + assert.isUndefined($H({}).get('constructor')); + }); + + test('#unset', function () { + var hash = $H(Fixtures.Hash.many); + assert.equal('B', hash.unset('b')); + assert.hashEqual({a:'A', c: 'C', d:'D#'}, hash); + assert.isUndefined(hash.unset('z')); + assert.hashEqual({a:'A', c: 'C', d:'D#'}, hash); + // not equivalent to Hash#remove + assert.equal('A', hash.unset('a', 'c')); + assert.hashEqual({c: 'C', d:'D#'}, hash); + }); + + test('#toObject', function () { + var hash = $H(Fixtures.Hash.many), object = hash.toObject(); + assert.isInstanceOf(object, Object); + assert.hashEqual(Fixtures.Hash.many, object); + assert.notStrictEqual(Fixtures.Hash.many, object); + hash.set('foo', 'bar'); + assert.hashNotEqual(object, hash.toObject()); + }); + + test('new Hash', function () { + var object = Object.clone(Fixtures.Hash.one); + var h = new Hash(object), h2 = $H(object); + assert.isInstanceOf(h, Hash); + assert.isInstanceOf(h2, Hash); + + assert.hashEqual({}, new Hash()); + assert.hashEqual(object, h); + assert.hashEqual(object, h2); + + h.set('foo', 'bar'); + assert.hashNotEqual(object, h); + + var clone = $H(h); + assert.isInstanceOf(clone, Hash); + assert.hashEqual(h, clone); + h.set('foo', 'foo'); + assert.hashNotEqual(h, clone); + assert.strictEqual($H, Hash.from); + }); + + test('#keys', function () { + assert.enumEqual([], $H({}).keys()); + assert.enumEqual(['a'], $H(Fixtures.Hash.one).keys()); + assert.enumEqual($w('a b c d'), $H(Fixtures.Hash.many).keys().sort()); + assert.enumEqual($w('plus quad'), $H(Fixtures.Hash.functions).keys().sort()); + }); + + test('#values', function () { + assert.enumEqual([], $H({}).values()); + assert.enumEqual(['A#'], $H(Fixtures.Hash.one).values()); + assert.enumEqual($w('A B C D#'), $H(Fixtures.Hash.many).values().sort()); + assert.enumEqual($w('function function'), + $H(Fixtures.Hash.functions).values().map(function(i){ return typeof i; })); + assert.equal(9, $H(Fixtures.Hash.functions).get('quad')(3)); + assert.equal(6, $H(Fixtures.Hash.functions).get('plus')(3)); + }); + + test('#index', function () { + assert.isUndefined($H().index('foo')); + + assert('a', $H(Fixtures.Hash.one).index('A#')); + assert('a', $H(Fixtures.Hash.many).index('A')); + assert.isUndefined($H(Fixtures.Hash.many).index('Z')); + + var hash = $H({a:1,b:'2',c:1}); + assert(['a','c'].include(hash.index(1))); + assert.isUndefined(hash.index('1')); + }); + + test('#merge', function () { + var h = $H(Fixtures.Hash.many); + assert.notStrictEqual(h, h.merge()); + assert.notStrictEqual(h, h.merge({})); + assert.isInstanceOf(h.merge(), Hash); + assert.isInstanceOf(h.merge({}), Hash); + assert.hashEqual(h, h.merge()); + assert.hashEqual(h, h.merge({})); + assert.hashEqual(h, h.merge($H())); + assert.hashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.merge({aaa: 'AAA'})); + assert.hashEqual({a:'A#', b:'B', c:'C', d:'D#' }, h.merge(Fixtures.Hash.one)); + }); + + test('#update', function () { + var h = $H(Fixtures.Hash.many); + assert.strictEqual(h, h.update()); + assert.strictEqual(h, h.update({})); + assert.hashEqual(h, h.update()); + assert.hashEqual(h, h.update({})); + assert.hashEqual(h, h.update($H())); + assert.hashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update({aaa: 'AAA'})); + assert.hashEqual({a:'A#', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update(Fixtures.Hash.one)); + }); + + test('#toQueryString', function () { + assert.equal('', $H({}).toQueryString()); + assert.equal('a%23=A', $H({'a#': 'A'}).toQueryString()); + assert.equal('a=A%23', $H(Fixtures.Hash.one).toQueryString()); + assert.equal('a=A&b=B&c=C&d=D%23', $H(Fixtures.Hash.many).toQueryString()); + assert.equal("a=b&c", $H(Fixtures.Hash.value_undefined).toQueryString()); + assert.equal("a=b&c", $H("a=b&c".toQueryParams()).toQueryString()); + assert.equal("a=b+d&c", $H("a=b+d&c".toQueryParams()).toQueryString()); + assert.equal("a=b&c=", $H(Fixtures.Hash.value_null).toQueryString()); + assert.equal("a=b&c=0", $H(Fixtures.Hash.value_zero).toQueryString()); + assert.equal("color=r&color=g&color=b", $H(Fixtures.Hash.multiple).toQueryString()); + assert.equal("color=r&color=&color=g&color&color=0", $H(Fixtures.Hash.multiple_nil).toQueryString()); + assert.equal("color=&color", $H(Fixtures.Hash.multiple_all_nil).toQueryString()); + assert.equal("", $H(Fixtures.Hash.multiple_empty).toQueryString()); + assert.equal("", $H({foo: {}, bar: {}}).toQueryString()); + assert.equal("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.Hash.multiple_special).toQueryString()); + assert.hashEqual(Fixtures.Hash.multiple_special, $H(Fixtures.Hash.multiple_special).toQueryString().toQueryParams()); + assert.strictEqual(Object.toQueryString, Hash.toQueryString); + + // Serializing newlines and spaces is weird. See: + // http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13.4.1 + var complex = "an arbitrary line\n\'something in single quotes followed by a newline\'\r\n" + + "and more text eventually"; + var queryString = $H({ val: complex }).toQueryString(); + var expected = "val=an+arbitrary+line%0D%0A'something+in+single+quotes+followed+by+a+" + + "newline'%0D%0Aand+more+text+eventually"; + assert.equal(expected, queryString, "newlines and spaces should be properly encoded"); + }); + + test('#inspect', function () { + assert.equal('#', $H({}).inspect()); + assert.equal("#", $H(Fixtures.Hash.one).inspect()); + assert.equal("#", $H(Fixtures.Hash.many).inspect()); + }); + + test('#clone', function () { + var h = $H(Fixtures.Hash.many); + assert.hashEqual(h, h.clone()); + assert.isInstanceOf(h.clone(), Hash); + assert.notStrictEqual(h, h.clone()); + }); + + test('#toJSON', function () { + assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', + Object.toJSON({b: [undefined, false, true, undefined], c: {a: 'hello!'}})); + }); + + test('ability to contain any key', function () { + var h = $H({ _each: 'E', map: 'M', keys: 'K', pluck: 'P', unset: 'U' }); + assert.enumEqual($w('_each keys map pluck unset'), h.keys().sort()); + assert.equal('U', h.unset('unset')); + assert.hashEqual({ _each: 'E', map: 'M', keys: 'K', pluck: 'P' }, h); + }); + + test('#toTemplateReplacements', function () { + var template = new Template("#{a} #{b}"), hash = $H({ a: "hello", b: "world" }); + assert.equal("hello world", template.evaluate(hash.toObject())); + assert.equal("hello world", template.evaluate(hash)); + assert.equal("hello", "#{a}".interpolate(hash)); + }); + + test("don't iterate over shadowed properties", function () { + // redundant now that object is systematically cloned. + var FooMaker = function(value) { + this.key = value; + }; + FooMaker.prototype.key = 'foo'; + var foo = new FooMaker('bar'); + assert.equal("key=bar", new Hash(foo).toQueryString()); + assert.equal("key=bar", new Hash(new Hash(foo)).toQueryString()); + }); + + test('#each', function () { + var h = $H({a:1, b:2}); + var result = []; + h.each(function(kv, i){ + result.push(i); + }); + assert.enumEqual([0,1], result); + }); + + +}); + + + + + + + + + + diff --git a/test.new/tests/layout.test.js b/test.new/tests/layout.test.js new file mode 100644 index 000000000..f182051b2 --- /dev/null +++ b/test.new/tests/layout.test.js @@ -0,0 +1,483 @@ +function isDisplayed(element) { + var originalElement = element; + + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; +} + +var IE8 = Prototype.Browser.IE && navigator.userAgent.indexOf('MSIE 8'); + +var documentViewportProperties = null; + +var RESIZE_DISABLED = false; + +suite("Layout",function(){ + this.name = 'layout'; + + setup(function () { + + if (documentViewportProperties) return; + + // Based on properties check from http://www.quirksmode.org/viewport/compatibility.html. + + documentViewportProperties = { + properties: [ + 'self.pageXOffset', 'self.pageYOffset', + 'self.screenX', 'self.screenY', + 'self.innerHeight', 'self.innerWidth', + 'self.outerHeight', 'self.outerWidth', + 'self.screen.height', 'self.screen.width', + 'self.screen.availHeight', 'self.screen.availWidth', + 'self.screen.availTop', 'self.screen.availLeft', + 'self.screen.Top', 'self.screen.Left', + 'self.screenTop', 'self.screenLeft', + 'document.body.clientHeight', 'document.body.clientWidth', + 'document.body.scrollHeight', 'document.body.scrollWidth', + 'document.body.scrollLeft', 'document.body.scrollTop', + 'document.body.offsetHeight', 'document.body.offsetWidth', + 'document.body.offsetTop', 'document.body.offsetLeft' + ].inject([], function (properties, prop) { + if (!self.screen && prop.include('self.screen')) return; + if (!document.body && prop.include('document.body')) return; + + properties.push(prop); + + if (prop.include('body') && document.documentElement) { + properties.push(prop.sub('.body', '.documentElement')); + } + + return properties; + }), + + inspect: function () { + var props = []; + this.properties.each(function (prop) { + if (eval(prop)) props[prop] = eval(prop); + }, this); + return props; + } + }; + }); + + + test("preCompute argument of layout", function() { + var preComputedLayout = $('box1').getLayout(true), + normalLayout = $('box1').getLayout(); + + // restore normal get method from Hash object + preComputedLayout.get = Hash.prototype.get; + + Element.Layout.PROPERTIES.each(function(key) { + assert.equal(normalLayout.get(key), preComputedLayout.get(key), key); + }); + }); + + test("layout on absolutely-positioned elements", function() { + var layout = $('box1').getLayout(); + + assert.equal(242, layout.get('width'), 'width' ); + assert.equal(555, layout.get('height'), 'height'); + + assert.equal(3, layout.get('border-left'), 'border-left'); + assert.equal(10, layout.get('padding-top'), 'padding-top'); + assert.equal(1020, layout.get('top'), 'top'); + + assert.equal(25, layout.get('left'), 'left'); + }); + + test("layout on elements with display: none and exact width", function() { + var layout = $('box2').getLayout(); + + assert(!isDisplayed($('box2')), 'box should be hidden'); + + assert.equal(500, layout.get('width'), 'width'); + assert.equal( 3, layout.get('border-right'), 'border-right'); + assert.equal( 10, layout.get('padding-bottom'), 'padding-bottom'); + assert.equal(526, layout.get('border-box-width'), 'border-box-width'); + + assert(!isDisplayed($('box2')), 'box should still be hidden'); + }); + + test("layout on elements with negative margins", function() { + var layout = $('box_with_negative_margins').getLayout(); + + assert.equal(-10, layout.get('margin-top') ); + assert.equal( -3, layout.get('margin-left') ); + assert.equal( 2, layout.get('margin-right')); + }); + + test("layout on elements with display: none and width: auto", function() { + var layout = $('box3').getLayout(); + + assert(!isDisplayed($('box3')), 'box should be hidden'); + + assert.equal(364, layout.get('width'), 'width'); + assert.equal(400, layout.get('margin-box-width'), 'margin-box-width'); + assert.equal(390, layout.get('border-box-width'), 'border-box-width'); + assert.equal(3, layout.get('border-right'), 'border-top'); + assert.equal(10, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + assert(!isDisplayed($('box3')), 'box should still be hidden'); + }); + + test("layout on elements with display: none ancestors",function() { + var layout = $('box4').getLayout(); + + assert(!isDisplayed($('box4')), 'box should be hidden'); + + // Width and height values are nonsensical for deeply-hidden elements. + assert.equal(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); + assert.equal(0, layout.get('margin-box-height'), 'height of a deeply-hidden element should be 0'); + + // But we can still get meaningful values for other measurements. + assert.equal(0, layout.get('border-right'), 'border-top'); + assert.equal(13, layout.get('padding-bottom'), 'padding-right'); + + // Ensure that we cleaned up after ourselves. + assert(!isDisplayed($('box4')), 'box should still be hidden'); + }); + + test("positioning on absolutely-positioned elements", function() { + var layout = $('box5').getLayout(); + + assert.equal(30, layout.get('top'), 'top'); + assert.equal(60, layout.get('right'), 'right (percentage value)'); + + assert.equal(340, layout.get('left'), 'left'); + }); + + test("positioning on absolutely-positioned element with top=0 and left=0", function() { + var layout = $('box6').getLayout(); + + assert.equal(0, layout.get('top'), 'top'); + if(IE8) + { + assert($('box6_parent') === $('box6').getOffsetParent()); + } + else + { + assert.strictEqual($('box6_parent'), $('box6').getOffsetParent()); + } + }); + + test("layout on statically-positioned element with percentage width", function() { + var layout = $('box7').getLayout(); + + assert.equal(150, layout.get('width')); + }); + + test("layout on absolutely-positioned element with percentage width", function() { + var layout = $('box8').getLayout(); + + assert.equal(150, layout.get('width')); + }); + + test("layout on fixed-position element with percentage width", function() { + var viewportWidth = document.viewport.getWidth(); + var layout = $('box9').getLayout(); + + info("NOTE: IE6 WILL fail these tests because it doesn't support position: fixed. This is expected."); + + function assertNear(v1, v2, message) { + var abs = Math.abs(v1 - v2); + assert(abs <= 1, message + ' (actual: ' + v1 + ', ' + v2 + ')'); + } + + // With percentage widths, we'll occasionally run into rounding + // discrepancies. Assert that the values agree to within 1 pixel. + var vWidth = viewportWidth / 4, eWidth = $('box9').measure('width'); + assertNear.call(this, vWidth, eWidth, 'width (visible)'); + + $('box9').hide(); + assertNear.call(this, vWidth, $('box9').measure('width'), 'width (hidden)'); + $('box9').show(); + }); + + test("#toCSS, #toObject, #toHash", function() { + var layout = $('box6').getLayout(); + var top = layout.get('top'); + + var cssObject = layout.toCSS('top'); + + assert('top' in cssObject, + "layout object should have 'top' property"); + + cssObject = layout.toCSS('top left bottom'); + + $w('top left bottom').each( function(prop) { + assert(prop in cssObject, "layout object should have '" + + prop + "' property"); + }, this); + + var obj = layout.toObject('top'); + assert('top' in obj, + "object should have 'top' property"); + }); + + test("dimensions on absolutely-positioned, hidden elements", function() { + var layout = $('box10').getLayout(); + + assert.equal(278, layout.get('width'), 'width' ); + assert.equal(591, layout.get('height'), 'height'); + }); + + // ELEMENT METHODS + + suite('Element', function () { + + test('#makeClipping, #undoClipping', function () { + var chained = Element.extend(document.createElement('DIV')); + assert.equal(chained, chained.makeClipping()); + assert.equal(chained, chained.makeClipping()); + assert.equal(chained, chained.makeClipping().makeClipping()); + + assert.equal(chained, chained.undoClipping()); + assert.equal(chained, chained.undoClipping()); + assert.equal(chained, chained.undoClipping().makeClipping()); + + ['hidden','visible','scroll'].each( function(overflowValue) { + var element = $('element_with_'+overflowValue+'_overflow'); + + assert.equal(overflowValue, element.getStyle('overflow')); + element.makeClipping(); + assert.equal('hidden', element.getStyle('overflow')); + element.undoClipping(); + assert.equal(overflowValue, element.getStyle('overflow')); + }); + }); + + test('#getHeight', function () { + assert.strictEqual(100, $('dimensions-visible').getHeight()); + assert.strictEqual(100, $('dimensions-display-none').getHeight()); + }); + + test('#getWidth', function () { + assert.strictEqual(200, $('dimensions-visible').getWidth(), '#dimensions-visible'); + assert.strictEqual(200, $('dimensions-display-none').getWidth(), '#dimensions-display-none'); + }); + + test('#getDimensions', function () { + assert.strictEqual(100, $('dimensions-visible').getDimensions().height); + assert.strictEqual(200, $('dimensions-visible').getDimensions().width); + assert.strictEqual(100, $('dimensions-display-none').getDimensions().height); + assert.strictEqual(200, $('dimensions-display-none').getDimensions().width); + + assert.strictEqual(100, $('dimensions-visible-pos-rel').getDimensions().height); + assert.strictEqual(200, $('dimensions-visible-pos-rel').getDimensions().width); + assert.strictEqual(100, $('dimensions-display-none-pos-rel').getDimensions().height); + assert.strictEqual(200, $('dimensions-display-none-pos-rel').getDimensions().width); + + assert.strictEqual(100, $('dimensions-visible-pos-abs').getDimensions().height); + assert.strictEqual(200, $('dimensions-visible-pos-abs').getDimensions().width); + assert.strictEqual(100, $('dimensions-display-none-pos-abs').getDimensions().height); + assert.strictEqual(200, $('dimensions-display-none-pos-abs').getDimensions().width); + + // known failing issue + // assert($('dimensions-nestee').getDimensions().width <= 500, 'check for proper dimensions of hidden child elements'); + + $('dimensions-td').hide(); + assert.strictEqual(100, $('dimensions-td').getDimensions().height); + assert.strictEqual(200, $('dimensions-td').getDimensions().width); + $('dimensions-td').show(); + + $('dimensions-tr').hide(); + assert.strictEqual(100, $('dimensions-tr').getDimensions().height); + assert.strictEqual(200, $('dimensions-tr').getDimensions().width); + $('dimensions-tr').show(); + + $('dimensions-table').hide(); + assert.strictEqual(100, $('dimensions-table').getDimensions().height); + assert.strictEqual(200, $('dimensions-table').getDimensions().width); + }); + + test('#positionedOffset', function () { + assert.enumEqual([10,10], + $('body_absolute').positionedOffset(), '#body_absolute'); + assert.enumEqual([10,10], + $('absolute_absolute').positionedOffset(), '#absolute_absolute'); + assert.enumEqual([10,10], + $('absolute_relative').positionedOffset(), '#absolute_relative'); + assert.enumEqual([0,10], + $('absolute_relative_undefined').positionedOffset(), '#absolute_relative_undefined'); + assert.enumEqual([10,10], + $('absolute_fixed_absolute').positionedOffset(), '#absolute_fixed_absolute'); + + var afu = $('absolute_fixed_undefined'); + assert.enumEqual([afu.offsetLeft, afu.offsetTop], + afu.positionedOffset(), '#absolute_fixed_undefined'); + + var element = new Element('div'), offset = element.positionedOffset(); + assert.enumEqual([0,0], offset, 'new element'); + assert.strictEqual(0, offset.top, 'new element top'); + assert.strictEqual(0, offset.left, 'new element left'); + }); + + test('#cumulativeOffset', function () { + var element = new Element('div'), offset = element.cumulativeOffset(); + assert.enumEqual([0,0], offset, 'new element'); + assert.strictEqual(0, offset.top, 'new element top'); + assert.strictEqual(0, offset.left, 'new element left'); + + var innerEl = new Element('div'), outerEl = new Element('div'); + outerEl.appendChild(innerEl); + assert.enumEqual([0,0], innerEl.cumulativeOffset(), 'new inner element'); + }); + + test('#viewportOffset', function () { + window.scrollTo(0, 0); + + assert.enumEqual([10, 10], + $('body_absolute').viewportOffset()); + assert.enumEqual([20,20], + $('absolute_absolute').viewportOffset()); + assert.enumEqual([20,20], + $('absolute_relative').viewportOffset()); + assert.enumEqual([20,30], + $('absolute_relative_undefined').viewportOffset()); + var element = new Element('div'), offset = element.viewportOffset(); + assert.enumEqual([0,0], offset); + assert.strictEqual(0, offset.top); + assert.strictEqual(0, offset.left); + }); + + test('#getOffsetParent', function () { + assert.equal('body_absolute', $('absolute_absolute').getOffsetParent().id, + '#body_absolute should be parent of #absolute_absolute'); + assert.equal('body_absolute', $('absolute_relative').getOffsetParent().id, + '#body_absolute should be parent of #absolute_relative'); + assert.equal('absolute_relative', $('inline').getOffsetParent().id, + '#absolute_relative should be parent of #inline'); + assert.equal('absolute_relative', $('absolute_relative_undefined').getOffsetParent().id, + '#absolute_relative should be parent of #absolute_relative_undefined'); + + assert.equal(document.body, new Element('div').getOffsetParent(), + 'body should be parent of unattached element'); + + [document, document.body, document.documentElement].each (function(node) { + assert.equal(document.body, Element.getOffsetParent(node)); + }); + }); + + test('#absolutize', function () { + $('notInlineAbsoluted', 'inlineAbsoluted').each(function(elt) { + if ('_originalLeft' in elt) delete elt._originalLeft; + elt.absolutize(); + assert.isUndefined(elt._originalLeft, 'absolutize() did not detect absolute positioning'); + }, this); + // invoking on "absolute" positioned element should return element + var element = $('absolute_fixed_undefined').setStyle({position: 'absolute'}); + assert.equal(element, element.absolutize()); + }); + + test('#relativize', function () { + // invoking on "relative" positioned element should return element + var element = $('absolute_fixed_undefined').setStyle({ + position: 'relative' }); + assert.equal(element, element.relativize()); + }); + + }); // Element + + suite('document.viewport', function () { + + test('#getDimensions', function () { + var original = document.viewport.getDimensions(); + + window.resizeTo(800, 600); + + wait(1000, function() { + var before = document.viewport.getDimensions(); + + var delta = { width: 800 - before.width, height: 600 - before.height }; + + window.resizeBy(50, 50); + wait(1000, function() { + var after = document.viewport.getDimensions(); + + // Assume that JavaScript window resizing is disabled if before width + // and after width are the same. + if (before.width === after.width) { + RESIZE_DISABLED = true; + info("SKIPPING REMAINING TESTS (JavaScript window resizing disabled)"); + return; + } + + assert.equal( + before.width + 50, after.width, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS" + ); + assert.equal( + before.height + 50, after.height, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THIS TEST TO PASS" + ); + + wait(1000, function() { + // Restore original dimensions. + window.resizeTo( + original.width + delta.width, + original.height + delta.height + ); + }); + }); + }); + }); + + test('#getDimensions (should not affect document properties)', function () { + // No properties on the document should be affected when resizing + // an absolutely-positioned (0,0) element to viewport dimensions. + var vd = document.viewport.getDimensions(); + + var before = documentViewportProperties.inspect(); + $('elementToViewportDimensions').setStyle({ height: vd.height + 'px', width: vd.width + 'px' }).show(); + var after = documentViewportProperties.inspect(); + $('elementToViewportDimensions').hide(); + + documentViewportProperties.properties.each(function(prop) { + assert.equal(before[prop], after[prop], prop + ' was affected'); + }); + }); + + test('#getScrollOffsets', function () { + var original = document.viewport.getDimensions(); + + window.scrollTo(0, 0); + assert.equal(0, document.viewport.getScrollOffsets().top); + + window.scrollTo(0, 35); + assert.equal(35, document.viewport.getScrollOffsets().top); + + if (RESIZE_DISABLED) { + info("SKIPPING REMAINING TESTS (JavaScript window resizing disabled)"); + return; + } + + window.resizeTo(200, 650); + + wait(1000, function() { + var before = document.viewport.getDimensions(); + var delta = { width: 200 - before.width, height: 650 - before.height }; + + window.scrollTo(25, 35); + assert.equal(25, document.viewport.getScrollOffsets().left, + "NOTE: YOU MUST ALLOW JAVASCRIPT TO RESIZE YOUR WINDOW FOR THESE TESTS TO PASS"); + + wait(1000, function() { + // Restore original dimensions. + window.resizeTo( + original.width + delta.width, + original.height + delta.height + ); + }); + }); + }); + + }); // document.viewport + +}); diff --git a/test.new/tests/number.test.js b/test.new/tests/number.test.js new file mode 100644 index 000000000..fe4042a4c --- /dev/null +++ b/test.new/tests/number.test.js @@ -0,0 +1,41 @@ + +suite('Number', function () { + this.name = 'number'; + + test('math methods', function () { + assert.equal(1, (0.9).round()); + assert.equal(-2, (-1.9).floor()); + assert.equal(-1, (-1.9).ceil()); + + $w('abs floor round ceil').each(function(method) { + assert.equal(Math[method](Math.PI), Math.PI[method]()); + }, this); + }); + + test('#toColorPart', function () { + assert.equal('00', (0).toColorPart()); + assert.equal('0a', (10).toColorPart()); + assert.equal('ff', (255).toColorPart()); + }); + + test('#toPaddedString', function () { + assert.equal('00', (0).toPaddedString(2, 16)); + assert.equal('0a', (10).toPaddedString(2, 16)); + assert.equal('ff', (255).toPaddedString(2, 16)); + assert.equal('000', (0).toPaddedString(3)); + assert.equal('010', (10).toPaddedString(3)); + assert.equal('100', (100).toPaddedString(3)); + assert.equal('1000', (1000).toPaddedString(3)); + }); + + test('#times', function () { + var results = []; + (5).times(function(i) { results.push(i); }); + assert.enumEqual($R(0, 4), results); + + results = []; + (5).times(function(i) { results.push(i * this.i); }, { i: 2 }); + assert.enumEqual([0, 2, 4, 6, 8], results); + }); + +}); diff --git a/test.new/tests/object.test.js b/test.new/tests/object.test.js new file mode 100644 index 000000000..a32c53b9d --- /dev/null +++ b/test.new/tests/object.test.js @@ -0,0 +1,231 @@ +var Person = function(name){ + this.name = name; +}; + +Person.prototype.toJSON = function() { + return '-' + this.name; +}; + +/// + +suite('Object', function () { + this.name = 'object'; + + test('.extend', function () { + var object = {foo: 'foo', bar: [1, 2, 3]}; + assert.strictEqual(object, Object.extend(object)); + assert.hashEqual({foo: 'foo', bar: [1, 2, 3]}, object); + assert.strictEqual(object, Object.extend(object, {bla: 123})); + assert.hashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object); + assert.hashEqual({foo: 'foo', bar: [1, 2, 3], bla: null}, + Object.extend(object, {bla: null})); + }); + + test('.toQueryString', function () { + assert.equal('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'})); + }); + + test('.clone', function () { + var object = {foo: 'foo', bar: [1, 2, 3]}; + assert.notStrictEqual(object, Object.clone(object)); + assert.hashEqual(object, Object.clone(object)); + assert.hashEqual({}, Object.clone()); + var clone = Object.clone(object); + delete clone.bar; + assert.hashEqual({foo: 'foo'}, clone, + "Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted."); + }); + + test('.keys', function () { + assert.enumEqual([], Object.keys({})); + assert.enumEqual(['bar', 'foo'], Object.keys({foo: 'foo', bar: 'bar'}).sort()); + function Foo() { this.bar = 'bar'; } + Foo.prototype.foo = 'foo'; + assert.enumEqual(['bar'], Object.keys(new Foo())); + assert.raise('TypeError', function(){ Object.keys(); }); + + var obj = { + foo: 'bar', + baz: 'thud', + toString: function() { return '1'; }, + valueOf: function() { return 1; } + }; + + assert.equal(4, Object.keys(obj).length, 'DontEnum properties should be included in Object.keys'); + }); + + test('.inspect', function () { + assert.equal('undefined', Object.inspect()); + assert.equal('undefined', Object.inspect(undefined)); + assert.equal('null', Object.inspect(null)); + assert.equal("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar')); + assert.equal('[]', Object.inspect([])); + assert.nothingRaised(function() { Object.inspect(window.Node); }); + }); + + test('.toJSON', function () { + assert.isUndefined(Object.toJSON(undefined)); + assert.isUndefined(Object.toJSON(Prototype.K)); + assert.equal('\"\"', Object.toJSON('')); + assert.equal('\"test\"', Object.toJSON('test')); + assert.equal('null', Object.toJSON(Number.NaN)); + assert.equal('0', Object.toJSON(0)); + assert.equal('-293', Object.toJSON(-293)); + assert.equal('[]', Object.toJSON([])); + assert.equal('[\"a\"]', Object.toJSON(['a'])); + assert.equal('[\"a\",1]', Object.toJSON(['a', 1])); + assert.equal('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}])); + assert.equal('{\"a\":\"hello!\"}', Object.toJSON({a: 'hello!'})); + assert.equal('{}', Object.toJSON({})); + assert.equal('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K})); + assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', + Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})); + assert.equal('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', + Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}))); + assert.equal('true', Object.toJSON(true)); + assert.equal('false', Object.toJSON(false)); + assert.equal('null', Object.toJSON(null)); + var sam = new Person('sam'); + assert.equal('"-sam"', Object.toJSON(sam)); + }); + + test('.toHTML', function () { + assert.strictEqual('', Object.toHTML()); + assert.strictEqual('', Object.toHTML('')); + assert.strictEqual('', Object.toHTML(null)); + assert.strictEqual('0', Object.toHTML(0)); + assert.strictEqual('123', Object.toHTML(123)); + assert.equal('hello world', Object.toHTML('hello world')); + assert.equal('hello world', Object.toHTML({toHTML: function() { return 'hello world'; }})); + }); + + test('.isArray', function () { + assert(Object.isArray([])); + assert(Object.isArray([0])); + assert(Object.isArray([0, 1])); + assert(!Object.isArray({})); + assert(!Object.isArray($('object-test-list').childNodes)); + assert(!Object.isArray()); + assert(!Object.isArray('')); + assert(!Object.isArray('foo')); + assert(!Object.isArray(0)); + assert(!Object.isArray(1)); + assert(!Object.isArray(null)); + assert(!Object.isArray(true)); + assert(!Object.isArray(false)); + assert(!Object.isArray(undefined)); + }); + + test('.isHash', function () { + assert(Object.isHash($H())); + assert(Object.isHash(new Hash())); + assert(!Object.isHash({})); + assert(!Object.isHash(null)); + assert(!Object.isHash()); + assert(!Object.isHash('')); + assert(!Object.isHash(2)); + assert(!Object.isHash(false)); + assert(!Object.isHash(true)); + assert(!Object.isHash([])); + }); + + test('.isElement', function () { + assert(Object.isElement(document.createElement('div'))); + assert(Object.isElement(new Element('div'))); + assert(Object.isElement($('object-test'))); + assert(!Object.isElement(document.createTextNode('bla'))); + + // falsy variables should not mess up return value type + assert.strictEqual(false, Object.isElement(0)); + assert.strictEqual(false, Object.isElement('')); + assert.strictEqual(false, Object.isElement(NaN)); + assert.strictEqual(false, Object.isElement(null)); + assert.strictEqual(false, Object.isElement(undefined)); + }); + + test('.isFunction', function () { + assert(Object.isFunction(function() { })); + assert(Object.isFunction(Class.create())); + + assert(!Object.isFunction("a string")); + assert(!Object.isFunction($(document.createElement('div')))); + assert(!Object.isFunction([])); + assert(!Object.isFunction({})); + assert(!Object.isFunction(0)); + assert(!Object.isFunction(false)); + assert(!Object.isFunction(undefined)); + assert(!Object.isFunction(/xyz/), 'regular expressions are not functions'); + }); + + test('.isString', function () { + assert(!Object.isString(function() { })); + assert(Object.isString("a string")); + assert(Object.isString(new String("a string"))); + assert(!Object.isString(0)); + assert(!Object.isString([])); + assert(!Object.isString({})); + assert(!Object.isString(false)); + assert(!Object.isString(undefined)); + assert(!Object.isString(document), 'host objects should return false rather than throw exceptions'); + }); + + test('.isNumber', function () { + assert(Object.isNumber(0)); + assert(Object.isNumber(1.0)); + assert(Object.isNumber(new Number(0))); + assert(Object.isNumber(new Number(1.0))); + assert(!Object.isNumber(function() { })); + assert(!Object.isNumber({ test: function() { return 3; } })); + assert(!Object.isNumber("a string")); + assert(!Object.isNumber([])); + assert(!Object.isNumber({})); + assert(!Object.isNumber(false)); + assert(!Object.isNumber(undefined)); + assert(!Object.isNumber(document), 'host objects should return false rather than throw exceptions'); + }); + + test('.isDate', function () { + var d = new Date(); + assert(Object.isDate(d), 'constructor with no arguments'); + assert(Object.isDate(new Date(0)), 'constructor with milliseconds'); + assert(Object.isDate(new Date(1995, 11, 17)), 'constructor with Y, M, D'); + assert(Object.isDate(new Date(1995, 11, 17, 3, 24, 0)), 'constructor with Y, M, D, H, M, S'); + assert(Object.isDate(new Date(Date.parse("Dec 25, 1995"))), 'constructor with result of Date.parse'); + + assert(!Object.isDate(d.valueOf()), 'Date#valueOf returns a number'); + assert(!Object.isDate(function() { })); + assert(!Object.isDate(0)); + assert(!Object.isDate("a string")); + assert(!Object.isDate([])); + assert(!Object.isDate({})); + assert(!Object.isDate(false)); + assert(!Object.isDate(undefined)); + assert(!Object.isDate(document), 'host objects should return false rather than throw exceptions'); + }); + + test('.isUndefined', function () { + assert(Object.isUndefined(undefined)); + assert(!Object.isUndefined(null)); + assert(!Object.isUndefined(false)); + assert(!Object.isUndefined(0)); + assert(!Object.isUndefined("")); + assert(!Object.isUndefined(function() { })); + assert(!Object.isUndefined([])); + assert(!Object.isUndefined({})); + }); + + test('should not extend Object.prototype', function () { + // for-in is supported with objects + var iterations = 0, obj = { a: 1, b: 2, c: 3 }, property; + for (property in obj) iterations++; + assert.equal(3, iterations); + + // for-in is not supported with arrays + iterations = 0; + var arr = [1,2,3]; + for (property in arr) iterations++; + assert(iterations > 3); + }); + + +}); diff --git a/test.new/tests/periodical_executer.test.js b/test.new/tests/periodical_executer.test.js new file mode 100644 index 000000000..f97d73582 --- /dev/null +++ b/test.new/tests/periodical_executer.test.js @@ -0,0 +1,38 @@ + +suite('PeriodicalExecuter', function () { + this.name = 'periodical_executer'; + + test('#stop', function () { + var peEventCount = 0; + function peEventFired(pe) { + if (++peEventCount > 2) pe.stop(); + } + + // peEventFired will stop the PeriodicalExecuter after 3 callbacks + new PeriodicalExecuter(peEventFired, 0.05); + + wait(600, function() { + assert.equal(3, peEventCount); + }); + }); + + test('#onTimerEvent', function () { + var pe = { + onTimerEvent: PeriodicalExecuter.prototype.onTimerEvent, + execute: function() { + assert(pe.currentlyExecuting); + } + }; + + pe.onTimerEvent(); + assert(!pe.currentlyExecuting); + + pe.execute = function() { + assert(pe.currentlyExecuting); + throw new Error(); + }; + assert.raise('Error', pe.onTimerEvent.bind(pe)); + assert(!pe.currentlyExecuting); + }); + +}); diff --git a/test.new/tests/position.test.js b/test.new/tests/position.test.js new file mode 100644 index 000000000..fec9acd73 --- /dev/null +++ b/test.new/tests/position.test.js @@ -0,0 +1,47 @@ + +suite('Position', function () { + this.name = 'position'; + + setup(function () { + scrollTo(0, 0); + Position.prepare(); + Position.includeScrollOffsets = false; + }); + + teardown(function () { + scrollTo(0, 0); + Position.prepare(); + Position.includeScrollOffsets = false; + }); + + test('.prepare', function () { + Position.prepare(); + assert.equal(0, Position.deltaX); + assert.equal(0, Position.deltaY); + scrollTo(20, 30); + Position.prepare(); + assert.equal(20, Position.deltaX); + assert.equal(30, Position.deltaY); + }); + + test('.within', function () { + [true, false].each(function(withScrollOffsets) { + Position.includeScrollOffsets = withScrollOffsets; + assert(!Position.within($('position_test_body_absolute'), 9, 9), 'outside left/top'); + assert(Position.within($('position_test_body_absolute'), 10, 10), 'left/top corner'); + assert(Position.within($('position_test_body_absolute'), 10, 19), 'left/bottom corner'); + assert(!Position.within($('position_test_body_absolute'), 10, 20), 'outside bottom'); + }, this); + + scrollTo(20, 30); + Position.prepare(); + Position.includeScrollOffsets = true; + assert(!Position.within($('position_test_body_absolute'), 9, 9), 'outside left/top'); + assert(Position.within($('position_test_body_absolute'), 10, 10), 'left/top corner'); + assert(Position.within($('position_test_body_absolute'), 10, 19), 'left/bottom corner'); + assert(!Position.within($('position_test_body_absolute'), 10, 20), 'outside bottom'); + }); + + + +}); diff --git a/test.new/tests/prototype.test.js b/test.new/tests/prototype.test.js new file mode 100644 index 000000000..3f4f9ce65 --- /dev/null +++ b/test.new/tests/prototype.test.js @@ -0,0 +1,49 @@ + +suite('Prototype', function () { + this.name = 'prototype'; + + test('browser detection', function () { + var results = $H(Prototype.Browser).map(function(engine){ + return engine; + }).partition(function(engine){ + return engine[1] === true; + }); + var trues = results[0], falses = results[1]; + + var ua = navigator.userAgent; + + info('User agent string is: ' + ua); + + assert(trues.size() == 0 || trues.size() == 1, + 'There should be only one or no browser detected.'); + + // we should have definite trues or falses here + trues.each(function(result) { + assert(result[1] === true); + }, this); + falses.each(function(result) { + assert(result[1] === false); + }, this); + + if (ua.indexOf('AppleWebKit/') > -1) { + info('Running on WebKit'); + assert(Prototype.Browser.WebKit); + } + + if (!!window.opera) { + info('Running on Opera'); + assert(Prototype.Browser.Opera); + } + + if (ua.indexOf('MSIE') > -1 && !window.opera) { + info('Running on IE'); + assert(Prototype.Browser.IE); + } + + if (ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') == -1) { + info('Running on Gecko'); + assert(Prototype.Browser.Gecko); + } + }); + +}); diff --git a/test.new/tests/range.test.js b/test.new/tests/range.test.js new file mode 100644 index 000000000..ede02dd60 --- /dev/null +++ b/test.new/tests/range.test.js @@ -0,0 +1,67 @@ + +suite('Range', function () { + this.name = 'range'; + + test('#include', function () { + assert(!$R(0, 0, true).include(0)); + assert($R(0, 0, false).include(0)); + + assert($R(0, 5, true).include(0)); + assert($R(0, 5, true).include(4)); + assert(!$R(0, 5, true).include(5)); + + assert($R(0, 5, false).include(0)); + assert($R(0, 5, false).include(5)); + assert(!$R(0, 5, false).include(6)); + }); + + test('#each', function () { + var results = []; + $R(0, 0, true).each(function(value) { + results.push(value); + }); + + assert.enumEqual([], results); + + results = []; + $R(0, 3, false).each(function(value) { + results.push(value); + }); + + assert.enumEqual([0, 1, 2, 3], results); + + results = []; + $R(2, 4, true).each(function(value, index) { + results.push(index); + }); + assert.enumEqual([0, 1], results); + }); + + test('#any', function () { + assert(!$R(1, 1, true).any()); + assert($R(0, 3, false).any(function(value) { + return value == 3; + })); + }); + + test('#all', function () { + assert($R(1, 1, true).all()); + assert($R(0, 3, false).all(function(value) { + return value <= 3; + })); + }); + + test('#toArray', function () { + assert.enumEqual([], $R(0, 0, true).toArray()); + assert.enumEqual([0], $R(0, 0, false).toArray()); + assert.enumEqual([0], $R(0, 1, true).toArray()); + assert.enumEqual([0, 1], $R(0, 1, false).toArray()); + assert.enumEqual([-3, -2, -1, 0, 1, 2], $R(-3, 3, true).toArray()); + assert.enumEqual([-3, -2, -1, 0, 1, 2, 3], $R(-3, 3, false).toArray()); + }); + + test('defaults to inclusive', function () { + assert.enumEqual($R(-3,3), $R(-3,3,false)); + }); + +}); diff --git a/test.new/tests/regexp.test.js b/test.new/tests/regexp.test.js new file mode 100644 index 000000000..ae2d04856 --- /dev/null +++ b/test.new/tests/regexp.test.js @@ -0,0 +1,46 @@ + +suite('RegExp', function () { + this.name = 'regexp'; + + test('#escape', function () { + assert.equal('word', RegExp.escape('word')); + assert.equal('\\/slashes\\/', RegExp.escape('/slashes/')); + assert.equal('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\')); + assert.equal('\\\\border of word', RegExp.escape('\\border of word')); + + assert.equal('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)')); + assert.equal('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]); + + assert.equal('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)')); + assert.equal('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]); + + assert.equal('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)')); + assert.equal('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]); + + assert.equal('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)')); + assert.equal('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]); + + assert.equal('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?', new RegExp(RegExp.escape('
    ')).exec('
    ')[0]); + + assert.equal('false', RegExp.escape(false)); + assert.equal('undefined', RegExp.escape()); + assert.equal('null', RegExp.escape(null)); + assert.equal('42', RegExp.escape(42)); + + assert.equal('\\\\n\\\\r\\\\t', RegExp.escape('\\n\\r\\t')); + assert.equal('\n\r\t', RegExp.escape('\n\r\t')); + assert.equal('\\{5,2\\}', RegExp.escape('{5,2}')); + + assert.equal( + '\\/\\(\\[\\.\\*\\+\\?\\^\\=\\!\\:\\$\\{\\}\\(\\)\\|\\[\\\\\\]\\\\\\\/\\\\\\\\\\]\\)\\/g', + RegExp.escape('/([.*+?^=!:${}()|[\\]\\/\\\\])/g') + ); + }); + +}); diff --git a/test.new/tests/selector.test.js b/test.new/tests/selector.test.js new file mode 100644 index 000000000..e48857cf8 --- /dev/null +++ b/test.new/tests/selector.test.js @@ -0,0 +1,413 @@ + +function reduce(arr) { + return arr.length > 1 ? arr : arr[0]; +} + + +suite('Selector', function () { + this.name = 'selector'; + + test('tag (div)', function () { + assert.enumEqual($A(document.getElementsByTagName('li')), $$('li')); + assert.enumEqual([$('strong')], $$('strong')); + assert.enumEqual([], $$('nonexistent')); + + var allNodes = $A(document.getElementsByTagName('*')).select( function(node) { + return node.tagName !== '!'; + }); + assert.enumEqual(allNodes, $$('*')); + }); + + test('ID (#some_id)', function () { + assert.enumEqual([$('fixtures')], $$('#fixtures')); + assert.enumEqual([], $$('#nonexistent')); + assert.enumEqual([$('troubleForm')], $$('#troubleForm')); + }); + + test('class (.some-class)', function () { + assert.enumEqual($('p', 'link_1', 'item_1'), $$('.first')); + assert.enumEqual([], $$('.second')); + }); + + test('tag + ID (div#some_id)', function () { + assert.enumEqual([$('strong')], $$('strong#strong')); + assert.enumEqual([], $$('p#strong')); + }); + + test('tag + class (div.some-class)', function () { + assert.enumEqual($('link_1', 'link_2'), $$('a.internal')); + assert.enumEqual([$('link_2')], $$('a.internal.highlight')); + assert.enumEqual([$('link_2')], $$('a.highlight.internal')); + assert.enumEqual([], $$('a.highlight.internal.nonexistent')); + }); + + test('id + class (#some_id.some-class)', function () { + assert.enumEqual([$('link_2')], $$('#link_2.internal')); + assert.enumEqual([$('link_2')], $$('.internal#link_2')); + assert.enumEqual([$('link_2')], $$('#link_2.internal.highlight')); + assert.enumEqual([], $$('#link_2.internal.nonexistent')); + }); + + test('tag + id + class (div#some_id.some-class)', function () { + assert.enumEqual([$('link_2')], $$('a#link_2.internal')); + assert.enumEqual([$('link_2')], $$('a.internal#link_2')); + assert.enumEqual([$('item_1')], $$('li#item_1.first')); + assert.enumEqual([], $$('li#item_1.nonexistent')); + assert.enumEqual([], $$('li#item_1.first.nonexistent')); + }); + + test('descendant combinator', function () { + assert.enumEqual($('em2', 'em', 'span'), $$('#fixtures a *')); + assert.enumEqual([$('p')], $$('div#fixtures p')); + }); + + test('combines results when multiple expressions are passed', function () { + assert.enumEqual( + $('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), + $$('#p a', ' ul#list li ') + ); + }); + + test('tag + attr existence (a[href])', function () { + assert.enumEqual($$('#fixtures h1'), $$('h1[class]'), 'h1[class]'); + assert.enumEqual($$('#fixtures h1'), $$('h1[CLASS]'), 'h1[CLASS]'); + assert.enumEqual([$('item_3')], $$('li#item_3[class]'), 'li#item_3[class]'); + }); + + test('tag + attr equality (a[href="#"])', function () { + assert.enumEqual($('link_1', 'link_2', 'link_3'), $$('#fixtures a[href="#"]')); + assert.enumEqual($('link_1', 'link_2', 'link_3'), $$('#fixtures a[href=#]')); + }); + + test('tag + attr whitespace-tokenized (a[class~="internal"])', function () { + assert.enumEqual($('link_1', 'link_2'), $$('a[class~="internal"]'), "a[class~=\"internal\"]"); + assert.enumEqual($('link_1', 'link_2'), $$('a[class~=internal]'), "a[class~=internal]"); + }); + + test('attr ([href])', function () { + assert.enumEqual($(document.body).select('a[href]'), $(document.body).select('[href]')); + assert.enumEqual($$('a[class~="internal"]'), $$('[class~=internal]')); + assert.enumEqual($$('*[id]'), $$('[id]')); + assert.enumEqual($('checked_radio', 'unchecked_radio'), $$('[type=radio]')); + assert.enumEqual($$('*[type=checkbox]'), $$('[type=checkbox]')); + assert.enumEqual($('with_title', 'commaParent'), $$('[title]')); + assert.enumEqual($$('#troubleForm *[type=radio]'), $$('#troubleForm [type=radio]')); + assert.enumEqual($$('#troubleForm *[type]'), $$('#troubleForm [type]')); + }); + + test('attr (with hyphen) ([foo-bar])', function () { + assert.enumEqual([$('attr_with_dash')], $$('[foo-bar]'), "attribute with hyphen"); + }); + + test('attr negation a[href!="#"]', function () { + assert.enumEqual($('item_2', 'item_3'), $$('#list li[id!="item_1"]')); + // assert.enumEqual([], $$('a[href!="#"]')); + }); + + test('attr (value with brackets) (input[name="brackets[5][]"])', function () { + assert.enumEqual( + $('chk_1', 'chk_2'), + $$('#troubleForm2 input[name="brackets[5][]"]') + ); + assert.enumEqual( + [$('chk_1')], + $$('#troubleForm2 input[name="brackets[5][]"]:checked') + ); + assert.enumEqual( + [$('chk_2')], + $$('#troubleForm2 input[name="brackets[5][]"][value=2]') + ); + try { + $$('#troubleForm2 input[name=brackets[5][]]'); + assert(false, 'Error not thrown'); + } catch (e) { + assert(true, 'Error thrown'); + } + }); + + test('attr (multiple) (div[style] p[id] strong)', function () { + assert.enumEqual([$('strong')], $$('div[style] p[id] strong'), 'div[style] p[id] strong'); + }); + + test('a (multiple) ([class~=external][href="#"])', function () { + assert.enumEqual([$('link_3')], $$('a[class~=external][href="#"]'), + 'a[class~=external][href="#"]'); + assert.enumEqual([], $$('a[class~=external][href!="#"]'), + 'a[class~=external][href!="#"]'); + }); + + test('.matchElements', function () { + assert.elementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3'); + assert.elementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2'); + assert.enumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last')); + assert.elementsMatch(Selector.matchElements($('fixtures').descendants(), '.inexistant, a.internal'), '#link_1', '#link_2'); + }); + + test('.findElement', function () { + assert.elementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first'); + assert.elementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2'); + assert.elementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li'); + assert.equal(undefined, Selector.findElement($('list').descendants(), 'em')); + }); + + test('Element#match', function () { + var span = $('dupL1'); + + // tests that should pass + assert(span.match('span')); + assert(span.match('span#dupL1')); + assert(span.match('div > span'), 'child combinator'); + assert(span.match('#dupContainer span'), 'descendant combinator'); + assert(span.match('#dupL1'), 'ID only'); + assert(span.match('span.span_foo'), 'class name 1'); + assert(span.match('span.span_bar'), 'class name 2'); + assert(span.match('span:first-child'), 'first-child pseudoclass'); + + assert(!span.match('span.span_wtf'), 'bogus class name'); + assert(!span.match('#dupL2'), 'different ID'); + assert(!span.match('div'), 'different tag name'); + assert(!span.match('span span'), 'different ancestry'); + assert(!span.match('span > span'), 'different parent'); + assert(!span.match('span:nth-child(5)'), 'different pseudoclass'); + + assert(!$('link_2').match('a[rel^=external]')); + assert($('link_1').match('a[rel^=external]')); + assert($('link_1').match('a[rel^="external"]')); + assert($('link_1').match("a[rel^='external']")); + + assert(span.match({ match: function(element) { return true; }}), 'custom selector'); + assert(!span.match({ match: function(element) { return false; }}), 'custom selector'); + }); + + test('attr (space in value) (cite[title="hello world!"])', function () { + assert.enumEqual([$('with_title')], $$('cite[title="hello world!"]')); + }); + + test('> combinator', function () { + assert.enumEqual($('link_1', 'link_2'), $$('p.first > a')); + assert.enumEqual($('father', 'uncle'), $$('div#grandfather > div')); + assert.enumEqual($('level2_1', 'level2_2'), $$('#level1>span')); + assert.enumEqual($('level2_1', 'level2_2'), $$('#level1 > span')); + assert.enumEqual($('level3_1', 'level3_2'), $$('#level2_1 > *')); + assert.enumEqual([], $$('div > #nonexistent')); + }); + + test('+ combinator', function () { + assert.enumEqual([$('uncle')], $$('div.brothers + div.brothers')); + assert.enumEqual([$('uncle')], $$('div.brothers + div')); + assert.equal($('level2_2'), reduce($$('#level2_1+span'))); + assert.equal($('level2_2'), reduce($$('#level2_1 + span'))); + assert.equal($('level2_2'), reduce($$('#level2_1 + *'))); + assert.enumEqual([], $$('#level2_2 + span')); + assert.equal($('level3_2'), reduce($$('#level3_1 + span'))); + assert.equal($('level3_2'), reduce($$('#level3_1 + *'))); + assert.enumEqual([], $$('#level3_2 + *')); + assert.enumEqual([], $$('#level3_1 + em')); + }); + + test('~ combinator', function () { + assert.enumEqual([$('list')], $$('#fixtures h1 ~ ul')); + assert.equal($('level2_2'), reduce($$('#level2_1 ~ span'))); + assert.enumEqual($('level2_2', 'level2_3'), reduce($$('#level2_1 ~ *'))); + assert.enumEqual([], $$('#level2_2 ~ span')); + assert.enumEqual([], $$('#level3_2 ~ *')); + assert.enumEqual([], $$('#level3_1 ~ em')); + assert.enumEqual([$('level3_2')], $$('#level3_1 ~ #level3_2')); + assert.enumEqual([$('level3_2')], $$('span ~ #level3_2')); + assert.enumEqual([], $$('div ~ #level3_2')); + assert.enumEqual([], $$('div ~ #level2_3')); + }); + + test('attr (weird operators)', function () { + assert.enumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string'); + assert.enumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string'); + assert.enumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring'); + assert.enumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^="level2_"]')); + assert.enumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^=level2_]')); + assert.enumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$="_1"]')); + assert.enumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$=_1]')); + assert.enumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*="2"]')); + assert.enumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*=2]')); + }); + + test('selectors with duplicates', function () { + assert.enumEqual($$('div div'), $$('div div').uniq()); + assert.enumEqual($('dupL2', 'dupL3', 'dupL4', 'dupL5'), $$('#dupContainer span span')); + }); + + test(':(first|last|only|nth|nth-last)-child', function () { + assert.enumEqual([$('level2_1')], $$('#level1>*:first-child')); + assert.enumEqual($('level2_1', 'level3_1', 'level_only_child'), $$('#level1 *:first-child')); + assert.enumEqual([$('level2_3')], $$('#level1>*:last-child')); + assert.enumEqual($('level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:last-child')); + assert.enumEqual([$('level2_3')], $$('#level1>div:last-child')); + assert.enumEqual([$('level2_3')], $$('#level1 div:last-child')); + assert.enumEqual([], $$('#level1>div:first-child')); + assert.enumEqual([], $$('#level1>span:last-child')); + assert.enumEqual($('level2_1', 'level3_1'), $$('#level1 span:first-child')); + assert.enumEqual([], $$('#level1:first-child')); + assert.enumEqual([], $$('#level1>*:only-child')); + assert.enumEqual([$('level_only_child')], $$('#level1 *:only-child')); + assert.enumEqual([], $$('#level1:only-child')); + assert.enumEqual([$('link_2')], $$('#p *:nth-last-child(2)'), 'nth-last-child'); + assert.enumEqual([$('link_2')], $$('#p *:nth-child(3)'), 'nth-child'); + assert.enumEqual([$('link_2')], $$('#p a:nth-child(3)'), 'nth-child'); + assert.enumEqual($('item_2', 'item_3'), $$('#list > li:nth-child(n+2)')); + assert.enumEqual($('item_1', 'item_2'), $$('#list > li:nth-child(-n+2)')); + }); + + test(':(first|last|nth|nth-last)-of-type', function () { + assert.enumEqual([$('link_2')], $$('#p a:nth-of-type(2)'), 'nth-of-type'); + assert.enumEqual([$('link_1')], $$('#p a:nth-of-type(1)'), 'nth-of-type'); + assert.enumEqual([$('link_2')], $$('#p a:nth-last-of-type(1)'), 'nth-last-of-type'); + assert.enumEqual([$('link_1')], $$('#p a:first-of-type'), 'first-of-type'); + assert.enumEqual([$('link_2')], $$('#p a:last-of-type'), 'last-of-type'); + }); + + test(':not', function () { + assert.enumEqual([$('link_2')], $$('#p a:not(a:first-of-type)'), 'first-of-type'); + assert.enumEqual([$('link_1')], $$('#p a:not(a:last-of-type)'), 'last-of-type'); + assert.enumEqual([$('link_2')], $$('#p a:not(a:nth-of-type(1))'), 'nth-of-type'); + assert.enumEqual([$('link_1')], $$('#p a:not(a:nth-last-of-type(1))'), 'nth-last-of-type'); + assert.enumEqual([$('link_2')], $$('#p a:not([rel~=nofollow])'), 'attribute 1'); + assert.enumEqual([$('link_2')], $$('#p a:not(a[rel^=external])'), 'attribute 2'); + assert.enumEqual([$('link_2')], $$('#p a:not(a[rel$=nofollow])'), 'attribute 3'); + assert.enumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"]) > em'), 'attribute 4'); + assert.enumEqual([$('item_2')], $$('#list li:not(#item_1):not(#item_3)'), 'adjacent :not clauses'); + assert.enumEqual([$('son')], $$('#grandfather > div:not(#uncle) #son')); + assert.enumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"]) em'), 'attribute 4 + all descendants'); + assert.enumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"])>em'), 'attribute 4 (without whitespace)'); + }); + + test(':enabled, :disabled, :checked', function () { + assert.enumEqual( + [$('disabled_text_field')], + $$('#troubleForm > *:disabled'), + ':disabled' + ); + assert.enumEqual( + $('troubleForm').getInputs().without($('disabled_text_field')), + $$('#troubleForm > *:enabled'), + ':enabled' + ); + assert.enumEqual( + $('checked_box', 'checked_radio'), + $$('#troubleForm *:checked'), + ':checked' + ); + }); + + test(':empty', function () { + $('level3_1').innerHTML = ""; + assert.enumEqual($('level3_1', 'level3_2', 'level2_3'), + $$('#level1 *:empty'), '#level1 *:empty'); + assert.enumEqual([], $$('#level_only_child:empty'), + 'newlines count as content!'); + }); + + test('identical results from equivalent selectors', function () { + assert.enumEqual($$('div.brothers'), $$('div[class~=brothers]')); + assert.enumEqual($$('div.brothers'), $$('div[class~=brothers].brothers')); + assert.enumEqual($$('div:not(.brothers)'), $$('div:not([class~=brothers])')); + assert.enumEqual($$('li ~ li'), $$('li:not(:first-child)')); + assert.enumEqual($$('ul > li'), $$('ul > li:nth-child(n)')); + assert.enumEqual($$('ul > li:nth-child(even)'), $$('ul > li:nth-child(2n)')); + assert.enumEqual($$('ul > li:nth-child(odd)'), $$('ul > li:nth-child(2n+1)')); + assert.enumEqual($$('ul > li:first-child'), $$('ul > li:nth-child(1)')); + assert.enumEqual($$('ul > li:last-child'), $$('ul > li:nth-last-child(1)')); + assert.enumEqual($$('ul > li:nth-child(n-999)'), $$('ul > li')); + assert.enumEqual($$('ul>li'), $$('ul > li')); + assert.enumEqual($$('#p a:not(a[rel$="nofollow"])>em'), $$('#p a:not(a[rel$="nofollow"]) > em')); + }); + + test('selectors that should return nothing', function () { + assert.enumEqual([], $$('span:empty > *')); + assert.enumEqual([], $$('div.brothers:not(.brothers)')); + assert.enumEqual([], $$('#level2_2 :only-child:not(:last-child)')); + assert.enumEqual([], $$('#level2_2 :only-child:not(:first-child)')); + }); + + test('$$ (separates selectors properly)', function () { + assert.enumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first,#item_3 , #troubleForm')); + assert.enumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first', '#item_3 , #troubleForm')); + assert.enumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]')); + assert.enumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]')); + }); + + test('$$ (extends all nodes)', function () { + var element = document.createElement('div'); + (3).times(function(){ + element.appendChild(document.createElement('div')); + }); + element.setAttribute('id', 'scratch_element'); + $$('body')[0].appendChild(element); + + var results = $$('#scratch_element div'); + assert(typeof results[0].show == 'function'); + assert(typeof results[1].show == 'function'); + assert(typeof results[2].show == 'function'); + }); + + test('copied nodes get included', function () { + assert.elementsMatch( + Selector.matchElements($('counted_container').descendants(), 'div'), + 'div.is_counted' + ); + $('counted_container').innerHTML += $('counted_container').innerHTML; + assert.elementsMatch( + Selector.matchElements($('counted_container').descendants(), 'div'), 'div.is_counted', + 'div.is_counted' + ); + }); + + test('#select (on detached nodes)', function () { + var wrapper = new Element("div"); + wrapper.update("
    "); + assert.isNotNullOrUndefined(wrapper.select('[id=myTD]')[0], + 'selecting: [id=myTD]'); + assert.isNotNullOrUndefined(wrapper.select('#myTD')[0], + 'selecting: #myTD'); + assert.isNotNullOrUndefined(wrapper.select('td')[0], + 'selecting: td'); + assert($$('#myTD').length == 0, + 'should not turn up in document-rooted search'); + }); + + test('#down', function () { + var a = $('dupL4'); + var b = $('dupContainer').down('#dupL4'); + + assert.equal(a, b); + }); + + test('#down (with dot and colon)', function () { + var a = $('dupL4_dotcolon'); + var b = $('dupContainer.withdot:active').down('#dupL4_dotcolon'); + var c = $('dupContainer.withdot:active').select('#dupL4_dotcolon'); + + assert.equal(a, b); + assert.enumEqual([a], c); + }); + + test('descendant selector bugs', function () { + var el = document.createElement('div'); + el.innerHTML = '
    '; + document.body.appendChild(el); + assert.equal(2, $(el).select('ul li').length); + document.body.removeChild(el); + }); + + test('.findElement (with index when elements are not in document order)', function () { + var ancestors = $("target_1").ancestors(); + assert.equal( + $("container_2"), + Selector.findElement(ancestors, "[container], .container", 0) + ); + assert.equal( + $("container_1"), + Selector.findElement(ancestors, "[container], .container", 1) + ); + }); + + +}); diff --git a/test.new/tests/selector_engine.test.js b/test.new/tests/selector_engine.test.js new file mode 100644 index 000000000..7c6d36035 --- /dev/null +++ b/test.new/tests/selector_engine.test.js @@ -0,0 +1,47 @@ + +suite('Selector engine', function () { + this.name = 'selector_engine'; + + test('.engine', function () { + assert(Prototype.Selector.engine); + }); + + test('.select', function () { + var elements = Prototype.Selector.select('.test_class'); + + assert(Object.isArray(elements)); + assert.equal(2, elements.length); + assert.equal('div_parent', elements[0].id); + assert.equal('div_child', elements[1].id); + }); + + test('.select (with context)', function () { + var elements = Prototype.Selector.select('.test_class', $('div_parent')); + + assert(Object.isArray(elements)); + assert.equal(1, elements.length); + assert.equal('div_child', elements[0].id); + }); + + test('.select (with empty result set)', function () { + var elements = Prototype.Selector.select('.non_existent'); + + assert(Object.isArray(elements)); + assert.equal(0, elements.length); + }); + + test('.match', function () { + var element = $('div_parent'); + + assert.equal(true, Prototype.Selector.match(element, '.test_class')); + assert.equal(false, Prototype.Selector.match(element, '.non_existent')); + }); + + test('.find', function () { + var elements = document.getElementsByTagName('*'), + expression = '.test_class'; + assert.equal('div_parent', Prototype.Selector.find(elements, expression).id); + assert.equal('div_child', Prototype.Selector.find(elements, expression, 1).id); + }); + +}); diff --git a/test.new/tests/string.test.js b/test.new/tests/string.test.js new file mode 100644 index 000000000..92f4ca7ee --- /dev/null +++ b/test.new/tests/string.test.js @@ -0,0 +1,571 @@ +var attackTarget; +var evalScriptsCounter = 0, + largeTextEscaped = '<span>test</span>', + largeTextUnescaped = 'test'; +(2048).times(function(){ + largeTextEscaped += ' ABC'; + largeTextUnescaped += ' ABC'; +}); + + +/// + +suite('String', function () { + this.name = 'string'; + + test('.interpret', function () { + assert.strictEqual('true', String.interpret(true)); + assert.strictEqual('123', String.interpret(123)); + assert.strictEqual('foo bar', String.interpret('foo bar')); + assert.strictEqual( + 'object string', + String.interpret({ toString: function (){ return 'object string'; } }) + ); + assert.strictEqual('0', String.interpret(0)); + assert.strictEqual('false', String.interpret(false)); + assert.strictEqual('', String.interpret(undefined)); + assert.strictEqual('', String.interpret(null)); + assert.strictEqual('', String.interpret('')); + }); + + test('#gsub (with replacement function)', function () { + var source = 'foo boo boz'; + + assert.equal('Foo Boo BoZ', + source.gsub(/[^o]+/, function(match) { + return match[0].toUpperCase(); + })); + assert.equal('f2 b2 b1z', + source.gsub(/o+/, function(match) { + return match[0].length; + })); + assert.equal('f0 b0 b1z', + source.gsub(/o+/, function(match) { + return match[0].length % 2; + })); + }); + + test('#gsub (with replacement string)', function () { + var source = 'foo boo boz'; + + assert.equal('foobooboz', + source.gsub(/\s+/, '')); + assert.equal(' z', + source.gsub(/(.)(o+)/, '')); + + assert.equal('ウィメンズ2007
    クルーズコレクション', + 'ウィメンズ2007\nクルーズコレクション'.gsub(/\n/,'
    ')); + assert.equal('ウィメンズ2007
    クルーズコレクション', + 'ウィメンズ2007\nクルーズコレクション'.gsub('\n','
    ')); + + assert.equal('barfbarobarobar barbbarobarobar barbbarobarzbar', + source.gsub('', 'bar')); + assert.equal('barfbarobarobar barbbarobarobar barbbarobarzbar', + source.gsub(new RegExp(''), 'bar')); + }); + + test('#gsub (with replacement template string)', function () { + var source = 'foo boo boz'; + + assert.equal('-oo-#{1}- -oo-#{1}- -o-#{1}-z', + source.gsub(/(.)(o+)/, '-#{2}-\\#{1}-')); + assert.equal('-foo-f- -boo-b- -bo-b-z', + source.gsub(/(.)(o+)/, '-#{0}-#{1}-')); + assert.equal('-oo-f- -oo-b- -o-b-z', + source.gsub(/(.)(o+)/, '-#{2}-#{1}-')); + assert.equal(' z', + source.gsub(/(.)(o+)/, '#{3}')); + }); + + test('#gsub (with troublesome characters)', function () { + assert.equal('ab', 'a|b'.gsub('|', '')); + assert.equal('ab', 'ab(?:)'.gsub('(?:)', '')); + assert.equal('ab', 'ab()'.gsub('()', '')); + assert.equal('ab', 'ab'.gsub('^', '')); + assert.equal('ab', 'a?b'.gsub('?', '')); + assert.equal('ab', 'a+b'.gsub('+', '')); + assert.equal('ab', 'a*b'.gsub('*', '')); + assert.equal('ab', 'a{1}b'.gsub('{1}', '')); + assert.equal('ab', 'a.b'.gsub('.', '')); + }); + + test('#gsub (with zero-length match)', function () { + assert.equal('ab', 'ab'.gsub('', '')); + assert.equal('a', 'a'.gsub(/b*/, 'c')); + assert.equal('abc', 'abc'.gsub(/b{0}/, '')); + }); + + test('#sub (with replacement function)', function () { + var source = 'foo boo boz'; + + assert.equal('Foo boo boz', + source.sub(/[^o]+/, function(match) { + return match[0].toUpperCase(); + }), 1); + assert.equal('Foo Boo boz', + source.sub(/[^o]+/, function(match) { + return match[0].toUpperCase(); + }, 2), 2); + assert.equal(source, + source.sub(/[^o]+/, function(match) { + return match[0].toUpperCase(); + }, 0), 0); + assert.equal(source, + source.sub(/[^o]+/, function(match) { + return match[0].toUpperCase(); + }, -1), -1); + }); + + test('#sub (with replacement string)', function () { + var source = 'foo boo boz'; + + assert.equal('oo boo boz', + source.sub(/[^o]+/, '')); + assert.equal('oooo boz', + source.sub(/[^o]+/, '', 2)); + assert.equal('-f-oo boo boz', + source.sub(/[^o]+/, '-#{0}-')); + assert.equal('-f-oo- b-oo boz', + source.sub(/[^o]+/, '-#{0}-', 2)); + }); + + test('#scan', function () { + var source = 'foo boo boz', results = []; + var str = source.scan(/[o]+/, function(match) { + results.push(match[0].length); + }); + assert.enumEqual([2, 2, 1], results); + assert.equal(source, source.scan(/x/, function () { + assert(false, 'this iterator should never get called'); + })); + assert(typeof str == 'string'); + window.debug = false; + }); + + test('#toArray', function () { + assert.enumEqual([],''.toArray()); + assert.enumEqual(['a'],'a'.toArray()); + assert.enumEqual(['a','b'],'ab'.toArray()); + assert.enumEqual(['f','o','o'],'foo'.toArray()); + }); + + test('#camelize', function () { + assert.equal('', ''.camelize()); + assert.equal('', '-'.camelize()); + assert.equal('foo', 'foo'.camelize()); + assert.equal('foo_bar', 'foo_bar'.camelize()); + assert.equal('FooBar', '-foo-bar'.camelize()); + assert.equal('FooBar', 'FooBar'.camelize()); + + assert.equal('fooBar', 'foo-bar'.camelize()); + assert.equal('borderBottomWidth', 'border-bottom-width'.camelize()); + + assert.equal('classNameTest','class-name-test'.camelize()); + assert.equal('classNameTest','className-test'.camelize()); + assert.equal('classNameTest','class-nameTest'.camelize()); + }); + + test('#capitalize', function () { + assert.equal('',''.capitalize()); + assert.equal('Ä','ä'.capitalize()); + assert.equal('A','A'.capitalize()); + assert.equal('Hello','hello'.capitalize()); + assert.equal('Hello','HELLO'.capitalize()); + assert.equal('Hello','Hello'.capitalize()); + assert.equal('Hello world','hello WORLD'.capitalize()); + }); + + test('#underscore', function () { + assert.equal('', ''.underscore()); + assert.equal('_', '-'.underscore()); + assert.equal('foo', 'foo'.underscore()); + assert.equal('foo', 'Foo'.underscore()); + assert.equal('foo_bar', 'foo_bar'.underscore()); + assert.equal('border_bottom', 'borderBottom'.underscore()); + assert.equal('border_bottom_width', 'borderBottomWidth'.underscore()); + assert.equal('border_bottom_width', 'border-Bottom-Width'.underscore()); }); + + test('#dasherize', function () { + assert.equal('', ''.dasherize()); + assert.equal('foo', 'foo'.dasherize()); + assert.equal('Foo', 'Foo'.dasherize()); + assert.equal('foo-bar', 'foo-bar'.dasherize()); + assert.equal('border-bottom-width', 'border_bottom_width'.dasherize()); + }); + + test('#truncate', function () { + var source = 'foo boo boz foo boo boz foo boo boz foo boo boz'; + assert.equal(source, source.truncate(source.length)); + assert.equal('foo boo boz foo boo boz foo...', source.truncate(0)); + assert.equal('fo...', source.truncate(5)); + assert.equal('foo b', source.truncate(5, '')); + + assert(typeof 'foo'.truncate(5) == 'string'); + assert(typeof 'foo bar baz'.truncate(5) == 'string'); + }); + + test('#strip', function () { + assert.equal('hello world', ' hello world '.strip()); + assert.equal('hello world', 'hello world'.strip()); + assert.equal('hello \n world', ' hello \n world '.strip()); + assert.equal('', ' '.strip()); + }); + + test('#stripTags', function () { + assert.equal('hello world', 'hello world'.stripTags()); + assert.equal('hello world', 'hello world'.stripTags()); + assert.equal('hello world', 'hello world'.stripTags()); + assert.equal('hello world', 'hello world'.stripTags()); + assert.equal('1\n2', '1\n2'.stripTags()); + assert.equal('one < two blah baz', 'one < two blah baz'.stripTags()); + }); + + test('#stripScripts', function () { + assert.equal('foo bar', 'foo bar'.stripScripts()); + assert.equal('foo bar', ('foo + + + + + + + + + <% @suites.each do |suite| %> + + <% end %> + + + + +
    + +
    + +
    + <%= yield %> +
    + + + + + + + + + \ No newline at end of file diff --git a/test.new/views/tests.erb b/test.new/views/tests.erb new file mode 100644 index 000000000..d2814f8f9 --- /dev/null +++ b/test.new/views/tests.erb @@ -0,0 +1,9 @@ + + +<% @suites.each do |suite| %> + <% if suite_has_html?(suite) %> +
    + <%= erb(:"tests/#{suite}", :locals => { :name => suite }) %> +
    + <% end %> +<% end %> \ No newline at end of file diff --git a/test.new/views/tests/ajax.erb b/test.new/views/tests/ajax.erb new file mode 100644 index 000000000..4af5e267a --- /dev/null +++ b/test.new/views/tests/ajax.erb @@ -0,0 +1,2 @@ +
    +
    diff --git a/test.new/views/tests/array.erb b/test.new/views/tests/array.erb new file mode 100644 index 000000000..8f091878c --- /dev/null +++ b/test.new/views/tests/array.erb @@ -0,0 +1 @@ +
    22
    diff --git a/test.new/views/tests/dom.erb b/test.new/views/tests/dom.erb new file mode 100644 index 000000000..a2aa472b3 --- /dev/null +++ b/test.new/views/tests/dom.erb @@ -0,0 +1,365 @@ + + +
    +

    Scroll test

    +
    + +
    visible
    + +
    visible
    + +
    visible
    + +
    visible
    + +
    + +
    + + + + + + + + + + + + +
    Data
    First Row
    Second Row
    +
    + +
    + + + + + + + + + + + + +
    Data
    First Row
    Second Row
    +
    + +

    Test paragraph outside of container

    + +
    +

    Test paragraph 1 inside of container

    +

    Test paragraph 2 inside of container

    +

    Test paragraph 3 inside of container

    +

    Test paragraph 4 inside of container

    +
    + +
    to be updated
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    some text

    some text

    + +
    V
    +
    H
    +
    S
    + +
    + +
    + +
    + + +
    +
    + + + + +
    +

    +
      +
    • +
    • +
    • +
    +
    +
    +
    + + +
    + +
    blah
    +blah +blah + +
    +
    +
    +
    + +
    test_csstext_1
    +
    test_csstext_2
    +
    test_csstext_3
    +
    test_csstext_4
    +
    test_csstext_5
    + +
    +
    +
    +
    + +
    + + + +
    + + + + + +
    + + +

    + +
    + + + + + +
    + + + + + +
    AB
    C
    DEF
    + +
    +
    + +
    +
    + +
    NFN
    +
    NFI
    +
    NFS
    + +
    FI
    +
    FS
    + +op2 +op2 +op3 +op3 +
    +
    +
    +
    +
    +
    + + + + + + +
    Data
    + +
    + +
    + + +

    +

    + + +

    +

    content

    +
    +
    +
    +
    +
    +
    +
    +
    + +

    some content.

    +

    some content.

    +

    some content.

    +

    some content.

    +
    +
    +
    + + + +
    +

    + + +
    +
    +
    +
    testtest
    +
    XYZ
    +
    +
    + foo + bar +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    \ No newline at end of file diff --git a/test.new/views/tests/element_mixins.erb b/test.new/views/tests/element_mixins.erb new file mode 100644 index 000000000..583d0e5f3 --- /dev/null +++ b/test.new/views/tests/element_mixins.erb @@ -0,0 +1,4 @@ +
    + + +
    \ No newline at end of file diff --git a/test.new/views/tests/enumerable.erb b/test.new/views/tests/enumerable.erb new file mode 100644 index 000000000..5e7e8f3b3 --- /dev/null +++ b/test.new/views/tests/enumerable.erb @@ -0,0 +1,8 @@ + + + + + + + +
    diff --git a/test.new/views/tests/event.erb b/test.new/views/tests/event.erb new file mode 100644 index 000000000..2c33b123c --- /dev/null +++ b/test.new/views/tests/event.erb @@ -0,0 +1,4 @@ + +
    diff --git a/test.new/views/tests/event_handler.erb b/test.new/views/tests/event_handler.erb new file mode 100644 index 000000000..c103f6415 --- /dev/null +++ b/test.new/views/tests/event_handler.erb @@ -0,0 +1,4 @@ + diff --git a/test.new/views/tests/form.erb b/test.new/views/tests/form.erb new file mode 100644 index 000000000..a43ea300b --- /dev/null +++ b/test.new/views/tests/form.erb @@ -0,0 +1,148 @@ +
    +
    + +
    This is not a form element
    + + + + +
    + +
    +
    + + + + + +
    + + +
    + + + + + +
    + + +
    + + + + +
    + + + +
    +
    + +
    + + + + + +
    This is not a form element
    + + + + +
    +
    + + + +
    + +
    + +
    + + +
    +
    +

    + + +
    + +
    +

    + + + +
    + +
    +

    + + + +
    + +
    + + + + + +
    + + +
    + +
    + + + + + +
    +
    diff --git a/test.new/views/tests/layout.erb b/test.new/views/tests/layout.erb new file mode 100644 index 000000000..f18e11b10 --- /dev/null +++ b/test.new/views/tests/layout.erb @@ -0,0 +1,302 @@ +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    +
    + + + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    +
    + + + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

    +
    + + + + +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + + + +
    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +
    +
    +
    + + + + +
    +
    + Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    +
    + + + + + +
    +
    + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +
    +
    + + + + +
    +
    + Duis aute. +
    +
    + + + + +
    + Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
    + + + + +
    +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    +
    + + + +
    V
    +
    H
    +
    S
    + +
    +
    +
    +
    +
    +
    + + + + + + +
    Data
    + +
    + +
    + + + + + +
    +
    +
    +
    testtest
    +
    XYZ
    +
    +
    + foo + bar +
    +
    +
    + + +
    + + + diff --git a/test.new/views/tests/object.erb b/test.new/views/tests/object.erb new file mode 100644 index 000000000..c2a7a0f3e --- /dev/null +++ b/test.new/views/tests/object.erb @@ -0,0 +1,6 @@ +
    +
      +
    • +
    • +
    • +
    \ No newline at end of file diff --git a/test.new/views/tests/position.erb b/test.new/views/tests/position.erb new file mode 100644 index 000000000..803b7f550 --- /dev/null +++ b/test.new/views/tests/position.erb @@ -0,0 +1,9 @@ +
    + +
    +
    +
    +
    testtest
    +
    +
    +
    diff --git a/test.new/views/tests/selector.erb b/test.new/views/tests/selector.erb new file mode 100644 index 000000000..4a47a1db7 --- /dev/null +++ b/test.new/views/tests/selector.erb @@ -0,0 +1,88 @@ + diff --git a/test.new/views/tests/selector_engine.erb b/test.new/views/tests/selector_engine.erb new file mode 100644 index 000000000..4e8c2079b --- /dev/null +++ b/test.new/views/tests/selector_engine.erb @@ -0,0 +1,4 @@ +
    +
    +
    +
    \ No newline at end of file From d4b69e59273bef91c221ff479689800adc133e5b Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 10 May 2014 13:40:47 -0500 Subject: [PATCH 429/502] Fix issue where the deprecated Element#childOf was not initially present on elements. We added it to the Element.Methods object in deprecated.js _after_ the initial call to Element.addMethods, meaning that it didn't get copied over unless the user made a subsequent call to Element.addMethods for whatever reason. Unit tests didn't catch this because they call Element.addMethods before testing starts. --- src/prototype/deprecated.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/prototype/deprecated.js b/src/prototype/deprecated.js index 26351b7a5..6859bd96f 100644 --- a/src/prototype/deprecated.js +++ b/src/prototype/deprecated.js @@ -4,7 +4,9 @@ Hash.toQueryString = Object.toQueryString; var Toggle = { display: Element.toggle }; -Element.Methods.childOf = Element.Methods.descendantOf; +Element.addMethods({ + childOf: Element.Methods.descendantOf +}); var Insertion = { Before: function(element, content) { @@ -202,7 +204,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); initialize: function(expression) { this.expression = expression.strip(); }, - + /** deprecated * Selector#findElements(root) -> [Element...] * - root (Element | document): A "scope" to search within. All results will @@ -214,7 +216,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); findElements: function(rootElement) { return Prototype.Selector.select(this.expression, rootElement); }, - + /** deprecated * Selector#match(element) -> Boolean * @@ -223,11 +225,11 @@ Object.extend(Element.ClassNames.prototype, Enumerable); match: function(element) { return Prototype.Selector.match(element, this.expression); }, - + toString: function() { return this.expression; }, - + inspect: function() { return "#"; } @@ -244,7 +246,7 @@ Object.extend(Element.ClassNames.prototype, Enumerable); matchElements: function(elements, expression) { var match = Prototype.Selector.match, results = []; - + for (var i = 0, length = elements.length; i < length; i++) { var element = elements[i]; if (match(element, expression)) { From 126d63dab2ceb30452b7099ed866542bf4afc23c Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 17:11:16 -0500 Subject: [PATCH 430/502] Remove the event registry from an element when its last observer is removed. --- src/prototype/dom/event.js | 397 +++++++++++++++++++------------------ test/functional/event.html | 108 +++++----- 2 files changed, 257 insertions(+), 248 deletions(-) diff --git a/src/prototype/dom/event.js b/src/prototype/dom/event.js index b9811a0ea..40e35e4e2 100644 --- a/src/prototype/dom/event.js +++ b/src/prototype/dom/event.js @@ -32,33 +32,33 @@ * The functions you're most likely to use a lot are [[Event.observe]], * [[Event.element]] and [[Event.stop]]. If your web app uses custom events, * you'll also get a lot of mileage out of [[Event.fire]]. - * + * * ##### Instance methods on event objects - * As of Prototype 1.6, all methods on the `Event` object are now also + * As of Prototype 1.6, all methods on the `Event` object are now also * available as instance methods on the event object itself: - * + * * **Before** - * + * * $('foo').observe('click', respondToClick); - * + * * function respondToClick(event) { * var element = Event.element(event); * element.addClassName('active'); * } - * + * * **After** - * + * * $('foo').observe('click', respondToClick); - * + * * function respondToClick(event) { * var element = event.element(); * element.addClassName('active'); * } - * + * * These methods are added to the event object through [[Event.extend]], - * in the same way that `Element` methods are added to DOM nodes through - * [[Element.extend]]. Events are extended automatically when handlers are - * registered with Prototype's [[Event.observe]] method; if you're using a + * in the same way that `Element` methods are added to DOM nodes through + * [[Element.extend]]. Events are extended automatically when handlers are + * registered with Prototype's [[Event.observe]] method; if you're using a * different method of event registration, for whatever reason,you'll need to * extend these events manually with [[Event.extend]]. **/ @@ -66,7 +66,7 @@ var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; - + var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, @@ -83,7 +83,7 @@ KEY_PAGEDOWN: 34, KEY_INSERT: 45 }; - + // We need to support three different event "modes": // 1. browsers with only DOM L2 Events (WebKit, FireFox); // 2. browsers with only IE's legacy events system (IE 6-8); @@ -108,7 +108,7 @@ isIELegacyEvent = function(event) { return true; }; } } - + // The two systems have different ways of indicating which button was used // for a mouse event. var _isButton; @@ -135,7 +135,7 @@ if (window.attachEvent) { if (!window.addEventListener) { // Legacy IE events only. - _isButton = _isButtonForLegacyEvents; + _isButton = _isButtonForLegacyEvents; } else { // Both systems are supported; decide at runtime. _isButton = function(event, code) { @@ -148,7 +148,7 @@ } else { _isButton = _isButtonForDOMEvents; } - + /** * Event.isLeftClick(@event) -> Boolean * - event (Event): An Event object @@ -183,37 +183,37 @@ * report clicks of the _left_ button as "left-clicks." **/ function isRightClick(event) { return _isButton(event, 2) } - + /** deprecated * Event.element(@event) -> Element * - event (Event): An Event object * * Returns the DOM element on which the event occurred. This method * is deprecated, use [[Event.findElement]] instead. - * + * * ##### Example - * + * * Here's a simple bit of code which hides any paragraph when directly clicked. - * + * * document.observe('click', function(event) { * var element = Event.element(event); * if ('P' == element.tagName) * element.hide(); * }); - * + * * ##### See also - * - * There is a subtle distinction between this function and + * + * There is a subtle distinction between this function and * [[Event.findElement]]. - * + * * ##### Note for Prototype 1.5.0 - * + * * Note that prior to version 1.5.1, if the browser does not support * *native DOM extensions* (see the [[Element]] section for further details), * the element returned by [[Event.element]] might very well * *not be extended*. If you intend to use methods from [[Element.Methods]] * on it, you need to wrap the call in the [[$]] function like so: - * + * * document.observe('click', function(event) { * var element = $(Event.element(event)); * // ... @@ -225,7 +225,7 @@ // internally as `_element` without having to extend the node. return Element.extend(_element(event)); } - + function _element(event) { event = Event.extend(event); @@ -256,14 +256,14 @@ * starting with the element on which the event occurred, then moving up * its ancestor chain. If `expression` is not given, the element which fired * the event is returned. - * + * * *If no matching element is found, `undefined` is returned.* - * + * * ##### Example - * + * * Here's a simple example that lets you click everywhere on the page and * hides the closest-fitting paragraph around your click (if any). - * + * * document.observe('click', function(event) { * var element = event.findElement('p'); * if (element) @@ -279,7 +279,7 @@ element = element.parentNode; } } - + /** * Event.pointer(@event) -> Object * @@ -341,33 +341,33 @@ * * Stopping an event also sets a `stopped` property on that event for * future inspection. - * + * * There are two aspects to how your browser handles an event once it fires up: - * - * 1. The browser usually triggers event handlers on the actual element the - * event occurred on, then on its parent element, and so on and so forth, - * until the document's root element is reached. This is called - * *event bubbling*, and is the most common form of event propagation. You - * may very well want to stop this propagation when you just handled an event, + * + * 1. The browser usually triggers event handlers on the actual element the + * event occurred on, then on its parent element, and so on and so forth, + * until the document's root element is reached. This is called + * *event bubbling*, and is the most common form of event propagation. You + * may very well want to stop this propagation when you just handled an event, * and don't want it to keep bubbling up (or see no need for it). - * - * 2. Once your code had a chance to process the event, the browser handles - * it as well, if that event has a *default behavior*. For instance, clicking - * on links navigates to them; submitting forms sends them over to the server - * side; hitting the Return key in a single-line form field submits it; etc. - * You may very well want to prevent this default behavior if you do your own + * + * 2. Once your code had a chance to process the event, the browser handles + * it as well, if that event has a *default behavior*. For instance, clicking + * on links navigates to them; submitting forms sends them over to the server + * side; hitting the Return key in a single-line form field submits it; etc. + * You may very well want to prevent this default behavior if you do your own * handling. - * - * Because stopping one of those aspects means, in 99.9% of the cases, - * preventing the other one as well, Prototype bundles both in this `stop` - * function. Calling it on an event object, stops propagation *and* prevents + * + * Because stopping one of those aspects means, in 99.9% of the cases, + * preventing the other one as well, Prototype bundles both in this `stop` + * function. Calling it on an event object, stops propagation *and* prevents * the default behavior. - * + * * ##### Example - * - * Here's a simple script that prevents a form from being sent to the server + * + * Here's a simple script that prevents a form from being sent to the server * side if certain field is empty. - * + * * Event.observe('signinForm', 'submit', function(event) { * var login = $F('login').strip(); * if ('' == login) { @@ -375,7 +375,7 @@ * // Display the issue one way or another * } * }); - **/ + **/ function stop(event) { Event.extend(event); event.preventDefault(); @@ -438,27 +438,27 @@ /** * Event.extend(@event) -> Event * - event (Event): An Event object - * + * * Extends `event` with all of the methods contained in `Event.Methods`. - * - * Note that all events inside handlers that were registered using + * + * Note that all events inside handlers that were registered using * [[Event.observe]] or [[Element.observe]] will be extended automatically. - * - * You need only call `Event.extend` manually if you register a handler a + * + * You need only call `Event.extend` manually if you register a handler a * different way (e.g., the `onclick` attribute). We really can't encourage * that sort of thing, though. **/ // IE's method for extending events. Event.extend = function(event, element) { if (!event) return false; - + // If it's not a legacy event, it doesn't need extending. if (!isIELegacyEvent(event)) return event; // Mark this event so we know not to extend a second time. if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; - + var pointer = Event.pointer(event); // The optional `element` argument gives us a fallback value for the @@ -469,24 +469,24 @@ pageX: pointer.x, pageY: pointer.y }); - + Object.extend(event, methods); Object.extend(event, additionalMethods); - + return event; }; } else { // Only DOM events, so no manual extending necessary. Event.extend = Prototype.K; } - + if (window.addEventListener) { // In all browsers that support DOM L2 Events, we can augment // `Event.prototype` directly. Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); } - + // // EVENT REGISTRY // @@ -494,14 +494,14 @@ mouseenter: 'mouseover', mouseleave: 'mouseout' }; - + function getDOMEventName(eventName) { return EVENT_TRANSLATIONS[eventName] || eventName; } - + if (MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) getDOMEventName = Prototype.K; - + function getUniqueElementID(element) { if (element === window) return 0; @@ -511,7 +511,7 @@ element._prototypeUID = Element.Storage.UID++; return element._prototypeUID; } - + // In Internet Explorer, DOM nodes have a `uniqueID` property. Saves us // from inventing our own. function getUniqueElementID_IE(element) { @@ -520,7 +520,7 @@ if (element == document) return 1; return element.uniqueID; } - + if ('uniqueID' in DIV) getUniqueElementID = getUniqueElementID_IE; @@ -539,17 +539,17 @@ if (!CACHE[uid]) CACHE[uid] = { element: element }; return CACHE[uid]; } - + function destroyRegistryForElement(element, uid) { if (Object.isUndefined(uid)) uid = getUniqueElementID(element); delete GLOBAL.Event.cache[uid]; } - + // The `register` and `unregister` functions handle creating the responder // and managing an event registry. They _don't_ attach and detach the // listeners themselves. - + // Add an event to the element's event registry. function register(element, eventName, handler) { var registry = getRegistryForElement(element); @@ -560,7 +560,7 @@ var i = entries.length; while (i--) if (entries[i].handler === handler) return null; - + var uid = getUniqueElementID(element); var responder = GLOBAL.Event._createResponder(uid, eventName, handler); var entry = { @@ -568,16 +568,16 @@ handler: handler }; - entries.push(entry); + entries.push(entry); return entry; } - + // Remove an event from the element's event registry. function unregister(element, eventName, handler) { var registry = getRegistryForElement(element); var entries = registry[eventName]; if (!entries) return; - + var i = entries.length, entry; while (i--) { if (entries[i].handler === handler) { @@ -585,7 +585,7 @@ break; } } - + // This handler wasn't in the collection, so it doesn't need to be // unregistered. if (!entry) return; @@ -593,11 +593,20 @@ // Remove the entry from the collection; var index = entries.indexOf(entry); entries.splice(index, 1); - + + if (entries.length === 0) { + // We can destroy the registry's entry for this event name... + delete registry[eventName]; + // ...and we we should destroy the whole registry if there are no other + // events. + if (Object.keys(registry).length === 1 && ('element' in registry)) + destroyRegistryForElement(element); + } + return entry; - } - - + } + + // // EVENT OBSERVING // @@ -751,22 +760,22 @@ * 1.6 also introduced setting the `this` context to the element being * observed, automatically extending the [[Event]] object, and the * [[Event#findElement]] method. - **/ + **/ function observe(element, eventName, handler) { element = $(element); var entry = register(element, eventName, handler); - + if (entry === null) return element; - var responder = entry.responder; + var responder = entry.responder; if (isCustomEvent(eventName)) observeCustomEvent(element, eventName, responder); else observeStandardEvent(element, eventName, responder); - + return element; } - + function observeStandardEvent(element, eventName, responder) { var actualEventName = getDOMEventName(eventName); if (element.addEventListener) { @@ -775,7 +784,7 @@ element.attachEvent('on' + actualEventName, responder); } } - + function observeCustomEvent(element, eventName, responder) { if (element.addEventListener) { element.addEventListener('dataavailable', responder, false); @@ -786,7 +795,7 @@ element.attachEvent('onlosecapture', responder); } } - + /** * Event.stopObserving(element[, eventName[, handler]]) -> Element * - element (Element | String): The element to stop observing, or its ID. @@ -848,38 +857,38 @@ * ...and then to remove: * * $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right - **/ + **/ function stopObserving(element, eventName, handler) { element = $(element); var handlerGiven = !Object.isUndefined(handler), eventNameGiven = !Object.isUndefined(eventName); - + if (!eventNameGiven && !handlerGiven) { stopObservingElement(element); return element; } - + if (!handlerGiven) { stopObservingEventName(element, eventName); return element; } - + var entry = unregister(element, eventName, handler); - - if (!entry) return element; + + if (!entry) return element; removeEvent(element, eventName, entry.responder); return element; } - + function stopObservingStandardEvent(element, eventName, responder) { var actualEventName = getDOMEventName(eventName); if (element.removeEventListener) { - element.removeEventListener(actualEventName, responder, false); + element.removeEventListener(actualEventName, responder, false); } else { element.detachEvent('on' + actualEventName, responder); } } - + function stopObservingCustomEvent(element, eventName, responder) { if (element.removeEventListener) { element.removeEventListener('dataavailable', responder, false); @@ -888,7 +897,7 @@ element.detachEvent('onlosecapture', responder); } } - + // The `stopObservingElement` and `stopObservingEventName` functions are // for bulk removal of event listeners. We use them rather than recurse @@ -917,29 +926,29 @@ removeEvent(element, eventName, entries[i].responder); } } - + // Stop observing all listeners of a certain event name on an element. function stopObservingEventName(element, eventName) { var registry = getRegistryForElement(element); var entries = registry[eventName]; if (!entries) return; delete registry[eventName]; - + var i = entries.length; while (i--) removeEvent(element, eventName, entries[i].responder); } - + function removeEvent(element, eventName, handler) { if (isCustomEvent(eventName)) stopObservingCustomEvent(element, eventName, handler); else stopObservingStandardEvent(element, eventName, handler); } - - - + + + // FIRING CUSTOM EVENTS function getFireTarget(element) { if (element !== document) return element; @@ -947,7 +956,7 @@ return document.documentElement; return element; } - + /** * Event.fire(element, eventName[, memo[, bubble = true]]) -> Event * - memo (?): Metadata for the event. Will be accessible to event @@ -960,50 +969,50 @@ **/ function fire(element, eventName, memo, bubble) { element = getFireTarget($(element)); - if (Object.isUndefined(bubble)) bubble = true; + if (Object.isUndefined(bubble)) bubble = true; memo = memo || {}; - + var event = fireEvent(element, eventName, memo, bubble); return Event.extend(event); } - + function fireEvent_DOM(element, eventName, memo, bubble) { var event = document.createEvent('HTMLEvents'); event.initEvent('dataavailable', bubble, true); - + event.eventName = eventName; event.memo = memo; - + element.dispatchEvent(event); return event; } - + function fireEvent_IE(element, eventName, memo, bubble) { var event = document.createEventObject(); event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; - + event.eventName = eventName; event.memo = memo; - - element.fireEvent(event.eventType, event); + + element.fireEvent(event.eventType, event); return event; } - + var fireEvent = document.createEvent ? fireEvent_DOM : fireEvent_IE; - - + + // EVENT DELEGATION - + /** * class Event.Handler - * + * * Creates an observer on an element that listens for a particular event on * that element's descendants, optionally filtering by a CSS selector. - * + * * This class simplifies the common "event delegation" pattern, in which one * avoids adding an observer to a number of individual elements and instead * listens on a _common ancestor_ element. - * + * * For more information on usage, see [[Event.on]]. **/ Event.Handler = Class.create({ @@ -1021,7 +1030,7 @@ * event. (If `selector` was given, this element will be the one that * satisfies the criteria described just above; if not, it will be the * one specified in the `element` argument). - * + * * Instantiates an `Event.Handler`. **Will not** begin observing until * [[Event.Handler#start]] is called. **/ @@ -1032,34 +1041,34 @@ this.callback = callback; this.handler = this.handleEvent.bind(this); }, - + /** * Event.Handler#start -> Event.Handler - * + * * Starts listening for events. Returns itself. **/ start: function() { Event.observe(this.element, this.eventName, this.handler); return this; }, - + /** * Event.Handler#stop -> Event.Handler - * + * * Stops listening for events. Returns itself. **/ stop: function() { Event.stopObserving(this.element, this.eventName, this.handler); return this; }, - + handleEvent: function(event) { var element = Event.findElement(event, this.selector); if (element) this.callback.call(this.element, event, element); } }); - + /** * Event.on(element, eventName[, selector], callback) -> Event.Handler * - element (Element | String): The DOM element to observe, or its ID. @@ -1075,53 +1084,53 @@ * satisfies the criteria described just above; if not, it will be the * one specified in the `element` argument). This function is **always** * bound to `element`. - * + * * Listens for events on an element's descendants, optionally filtering * to match a given CSS selector. - * + * * Creates an instance of [[Event.Handler]], calls [[Event.Handler#start]], * then returns that instance. Keep a reference to this returned instance if * you later want to unregister the observer. - * + * * ##### Usage - * + * * `Event.on` can be used to set up event handlers with or without event * delegation. In its simplest form, it works just like [[Event.observe]]: - * + * * $("messages").on("click", function(event) { * // ... * }); - * + * * An optional second argument lets you specify a CSS selector for event * delegation. This encapsulates the pattern of using [[Event#findElement]] * to retrieve the first ancestor element matching a specific selector. - * + * * $("messages").on("click", "a.comment", function(event, element) { * // ... * }); - * + * * Note the second argument in the handler above: it references the * element matched by the selector (in this case, an `a` tag with a class * of `comment`). This argument is important to use because within the * callback, the `this` keyword **will always refer to the original * element** (in this case, the element with the id of `messages`). - * + * * `Event.on` differs from `Event.observe` in one other important way: * its return value is an instance of [[Event.Handler]]. This instance * has a `stop` method that will remove the event handler when invoked * (and a `start` method that will attach the event handler again after * it's been removed). - * + * * // Register the handler: * var handler = $("messages").on("click", "a.comment", * this.click.bind(this)); - * + * * // Unregister the handler: * handler.stop(); - * + * * // Re-register the handler: * handler.start(); - * + * * This means that, unlike `Event.stopObserving`, there's no need to * retain a reference to the handler function. **/ @@ -1130,10 +1139,10 @@ if (Object.isFunction(selector) && Object.isUndefined(callback)) { callback = selector, selector = null; } - + return new Event.Handler(element, eventName, selector, callback).start(); } - + Object.extend(Event, Event.Methods); Object.extend(Event, { @@ -1150,41 +1159,41 @@ * See [[Event.fire]]. * * Fires a custom event with the current element as its target. - * + * * [[Element.fire]] creates a custom event with the given name, then triggers * it on the given element. The custom event has all the same properties * and methods of native events. Like a native event, it will bubble up * through the DOM unless its propagation is explicitly stopped. - * + * * The optional second argument will be assigned to the `memo` property of * the event object so that it can be read by event handlers. - * + * * Custom events are dispatched synchronously: [[Element.fire]] waits until * the event finishes its life cycle, then returns the event itself. - * + * * ##### Note - * + * * [[Element.fire]] does not support firing native events. All custom event * names _must_ be namespaced (using a colon). This is to avoid custom * event names conflicting with non-standard native DOM events such as * `mousewheel` and `DOMMouseScroll`. - * + * * ##### Examples - * + * * document.observe("widget:frobbed", function(event) { * console.log("Element with ID (" + event.target.id + * ") frobbed widget #" + event.memo.widgetNumber + "."); * }); - * + * * var someNode = $('foo'); * someNode.fire("widget:frobbed", { widgetNumber: 19 }); - * + * * //-> "Element with ID (foo) frobbed widget #19." - * + * * ##### Tip - * + * * Events that have been stopped with [[Event.stop]] will have a boolean - * `stopped` property set to true. Since [[Element.fire]] returns the custom + * `stopped` property set to true. Since [[Element.fire]] returns the custom * event, you can inspect this property to determine whether the event was * stopped. **/ @@ -1203,7 +1212,7 @@ * See [[Event.stopObserving]]. **/ stopObserving: stopObserving, - + /** * Element.on(@element, eventName[, selector], callback) -> Element * @@ -1238,13 +1247,13 @@ * * Listens for the given event over the entire document. Can also be used * for listening to `"dom:loaded"` event. - * + * * [[document.observe]] is the document-wide version of [[Element#observe]]. * Using [[document.observe]] is equivalent to * `Event.observe(document, eventName, handler)`. - * + * * ##### The `"dom:loaded"` event - * + * * One really useful event generated by Prototype that you might want to * observe on the document is `"dom:loaded"`. On supporting browsers it * fires on `DOMContentLoaded` and on unsupporting browsers it simulates it @@ -1254,9 +1263,9 @@ * fully loaded. The `load` event on `window` only fires after all page * images are loaded, making it unsuitable for some initialization purposes * like hiding page elements (so they can be shown later). - * + * * ##### Example - * + * * document.observe("dom:loaded", function() { * // initially hide all containers for tab content * $$('div.tabcontent').invoke('hide'); @@ -1268,15 +1277,15 @@ * document.stopObserving([eventName[, handler]]) -> Element * * Unregisters an event handler from the document. - * + * * [[document.stopObserving]] is the document-wide version of * [[Element.stopObserving]]. **/ stopObserving: stopObserving.methodize(), - + /** * document.on(@element, eventName[, selector], callback) -> Event.Handler - * + * * See [[Event.on]]. **/ on: on.methodize(), @@ -1292,74 +1301,74 @@ // Export to the global scope. if (GLOBAL.Event) Object.extend(window.Event, Event); else GLOBAL.Event = Event; - + GLOBAL.Event.cache = {}; - + function destroyCache_IE() { GLOBAL.Event.cache = null; } - + if (window.attachEvent) window.attachEvent('onunload', destroyCache_IE); - + DIV = null; docEl = null; })(this); -(function(GLOBAL) { +(function(GLOBAL) { /* Code for creating leak-free event responders is based on work by John-David Dalton. */ - + var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; - + function isSimulatedMouseEnterLeaveEvent(eventName) { return !MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === 'mouseenter' || eventName === 'mouseleave'); } - + // The functions for creating responders accept the element's UID rather // than the element itself. This way, there are _no_ DOM objects inside the // closure we create, meaning there's no need to unregister event listeners // on unload. - function createResponder(uid, eventName, handler) { + function createResponder(uid, eventName, handler) { if (Event._isCustomEvent(eventName)) - return createResponderForCustomEvent(uid, eventName, handler); + return createResponderForCustomEvent(uid, eventName, handler); if (isSimulatedMouseEnterLeaveEvent(eventName)) return createMouseEnterLeaveResponder(uid, eventName, handler); - + return function(event) { if (!Event.cache) return; - + var element = Event.cache[uid].element; Event.extend(event, element); handler.call(element, event); }; } - + function createResponderForCustomEvent(uid, eventName, handler) { return function(event) { var element = Event.cache[uid].element; if (Object.isUndefined(event.eventName)) return false; - + if (event.eventName !== eventName) return false; - + Event.extend(event, element); handler.call(element, event); }; } - + function createMouseEnterLeaveResponder(uid, eventName, handler) { return function(event) { var element = Event.cache[uid].element; - + Event.extend(event, element); var parent = event.relatedTarget; - + // Walk up the DOM tree to see if the related target is a descendant of // the original element. If it is, we ignore the event to match the // behavior of mouseenter/mouseleave. @@ -1367,12 +1376,12 @@ try { parent = parent.parentNode; } catch(e) { parent = element; } } - - if (parent === element) return; + + if (parent === element) return; handler.call(element, event); } } - + GLOBAL.Event._createResponder = createResponder; docEl = null; })(this); @@ -1380,23 +1389,23 @@ (function(GLOBAL) { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - + var TIMER; - + function fireContentLoadedEvent() { if (document.loaded) return; if (TIMER) window.clearTimeout(TIMER); document.loaded = true; document.fire('dom:loaded'); } - + function checkReadyState() { if (document.readyState === 'complete') { document.detachEvent('onreadystatechange', checkReadyState); fireContentLoadedEvent(); } } - + function pollDoScroll() { try { document.documentElement.doScroll('left'); @@ -1404,7 +1413,7 @@ TIMER = pollDoScroll.defer(); return; } - + fireContentLoadedEvent(); } @@ -1416,7 +1425,7 @@ fireContentLoadedEvent(); return; } - + if (document.addEventListener) { // All browsers that support DOM L2 Events support DOMContentLoaded, // including IE 9. @@ -1425,7 +1434,7 @@ document.attachEvent('onreadystatechange', checkReadyState); if (window == top) TIMER = pollDoScroll.defer(); } - + // Worst-case fallback. Event.observe(window, 'load', fireContentLoadedEvent); })(this); diff --git a/test/functional/event.html b/test/functional/event.html index 742c62bee..c9763400e 100644 --- a/test/functional/event.html +++ b/test/functional/event.html @@ -5,7 +5,7 @@ Prototype functional test file - + - + - +

    Scope test - scope of the handler should be this element

    - +

    Event object test - should be present as a first argument

    - +

    Hijack link test (preventDefault)

    - + - +

    Mouse click: left middle right

    - + - +

    Context menu event (tries to prevent default)

    - +

    Event.element() test

    - +

    Event.currentTarget test

    - + - +

    Event.findElement() test

    - + - +

    Stop propagation test (bubbling)

    - + - +

    Keyup test - focus on the textarea and type

    - + - +

    bindAsEventListener() test

    - + - +

    Object.inspect(event) test

    - + - +

    mouseenter test

    - + - +

    Add unload events

    - + - +
    Event delegation
      @@ -294,8 +294,8 @@

      Prototype functional tests for the Event module

      Child 3 (mouseup)
    - - Results: + + Results:
    • Test 1
    • @@ -303,7 +303,7 @@

      Prototype functional tests for the Event module

    • Test 3
    - + - - + + - + - - + + From 760c9bd90cc4b1db79b2fcc04147b2de72820143 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 17:23:53 -0500 Subject: [PATCH 431/502] Forgot to add the Rakefile. --- Rakefile | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Rakefile b/Rakefile index 0df098ec5..e8865a1ac 100755 --- a/Rakefile +++ b/Rakefile @@ -99,6 +99,16 @@ EOF }) end + def self.require_package(name) + begin + require name + rescue LoadError => e + puts "You need the #{name} package. Try installing it with:\n" + puts " $ gem install #{name}" + exit + end + end + def self.syntax_highlighter if ENV['SYNTAX_HIGHLIGHTER'] highlighter = ENV['SYNTAX_HIGHLIGHTER'].to_sym @@ -347,6 +357,31 @@ task :clean_tmp do puts '"rake clean_tmp" is deprecated. Please use "rake test:clean" instead.' end +namespace :test_new do + desc 'Starts the test server.' + task :start => [:require] do + path_to_app = File.join(PrototypeHelper::ROOT_DIR, 'test.new', 'server.rb') + require path_to_app + + puts "Unit tests available at " + UnitTests.run! + end + + task :require do + PrototypeHelper.require_package('sinatra') + end + + desc "Opens the test suite in several different browsers. (Does not start or stop the server; you should do that separately.)" + task :run => [:require] do + browsers, tests, grep = ENV['BROWSERS'], ENV['TESTS'], ENV['GREP'] + path_to_runner = File.join(PrototypeHelper::ROOT_DIR, 'test.new', 'runner.rb') + require path_to_runner + + Runner::run(browsers, tests, grep) + end + +end + namespace :caja do task :test => ['test:build', 'test:run'] From 0312723df7ec85ce8a631db8408ae14d7512f47d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 18:12:49 -0500 Subject: [PATCH 432/502] Remove old file. --- test.new/tests/array.old.test.js | 404 ------------------------------- 1 file changed, 404 deletions(-) delete mode 100644 test.new/tests/array.old.test.js diff --git a/test.new/tests/array.old.test.js b/test.new/tests/array.old.test.js deleted file mode 100644 index 5da6be7df..000000000 --- a/test.new/tests/array.old.test.js +++ /dev/null @@ -1,404 +0,0 @@ -var globalArgsTest = 'nothing to see here'; - -suite('Array', function() { - this.timeout(0); - this.name = 'array'; - - - test('$A({}) should equal []', function() { - assert.deepEqual([], $A({}), "$A({}) != []") - }); - - test('use $A() on function arguments', function() { - function toArrayOnArguments() { - globalArgsTest = $A(arguments); - } - - toArrayOnArguments(); - assert.deepEqual([], globalArgsTest, "globalArgsTest != []"); - - toArrayOnArguments('foo'); - assert.deepEqual(['foo'], globalArgsTest, "globalArgsTest != ['foo']"); - - toArrayOnArguments('foo', 'bar'); - assert.deepEqual(['foo', 'bar'], globalArgsTest, - "globalArgsTest != ['foo', 'bar']"); - }); - - test('use $A() On NodeList', function() { - // direct NodeList - assert( - 0 === $A($('testfixture').childNodes).length, - 'HTML childNodes length != 0' - ); - - // DOM - var element = document.createElement('div'); - element.appendChild(document.createTextNode('22')); - (2).times(function () { - element.appendChild(document.createElement('span')); - }); - assert( - 3 === $A(element.childNodes).length, - 'DOM childNodes length != 3' - ); - - // HTML String - element = document.createElement('div'); - $(element).update('22 2; - })); - assert(![1, 2, 3, 4, 5].any(function(value) { - return value > 5; - })); - - var x = [1, 2, 3], traversed = []; - delete x[1]; - x.any(function(val) { traversed.push(val); }); - assert.deepEqual([1, 3], traversed); - assert(2 === traversed.length); - }); - test(".all() method", function() { - assert([].all()); - - assert([true, true, true].all()); - assert(![true, false, false].all()); - assert(![false, false, false].all()); - - assert([1, 2, 3, 4, 5].all(function(value) { - return value > 0; - })); - assert(![1, 2, 3, 4, 5].all(function(value) { - return value > 1; - })); - - var x = [1, 2, 3], traversed = []; - delete x[1]; - x.all(function(val) { traversed.push(val); return true; }); - assert.deepEqual([1, 3], traversed); - assert(2, traversed.length); - }); - }); - - test("$w()", function() { - assert.deepEqual(['a', 'b', 'c', 'd'], $w('a b c d')); - assert.deepEqual([], $w(' ')); - assert.deepEqual([], $w('')); - assert.deepEqual([], $w(null)); - assert.deepEqual([], $w(undefined)); - assert.deepEqual([], $w()); - assert.deepEqual([], $w(10)); - assert.deepEqual(['a'], $w('a')); - assert.deepEqual(['a'], $w('a ')); - assert.deepEqual(['a'], $w(' a')); - assert.deepEqual(['a', 'b', 'c', 'd'], $w(' a b\nc\t\nd\n')); - }); - - - test(".each() On Sparse Arrays", function() { - var counter = 0; - - var sparseArray = [0, 1]; - sparseArray[5] = 5; - sparseArray.each( function(item) { counter++; }); - - assert(3 === counter, "Array#each should skip nonexistent keys in an array"); - }); - - test(".map() Generic", function() { - var result = Array.prototype.map.call({0:0, 1:1, length:2}); - assert.deepEqual([0, 1], result); - }); - - - test(".findAll() Generic", function() { - var result = Array.prototype.findAll.call({0:0, 1:1, length:2}, function(x) { - return x === 1; - }); - assert.deepEqual([1], result); - }); - - - test(".any() Generic", function() { - assert(Array.prototype.any.call({ 0:false, 1:true, length:2 })); - assert(!Array.prototype.any.call({ 0:false, 1:false, length:2 })); - }); - - - test(".all() Generic", function() { - assert(Array.prototype.all.call({ 0:true, 1:true, length:2 })); - assert(!Array.prototype.all.call({ 0:false, 1:true, length:2 })); - }); - -}); \ No newline at end of file From 73e4e3ff8e404804c4db425d49a9107c37c56a8e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 18:22:35 -0500 Subject: [PATCH 433/502] =?UTF-8?q?Don=E2=80=99t=20try=20to=20call=20`cons?= =?UTF-8?q?ole.group`=20unless=20the=20browser=20supports=20it.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test.new/static/js/test_helpers.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test.new/static/js/test_helpers.js b/test.new/static/js/test_helpers.js index 0c5b02f48..441c0b279 100644 --- a/test.new/static/js/test_helpers.js +++ b/test.new/static/js/test_helpers.js @@ -12,7 +12,10 @@ } window.info = info; - // A function that acts like setTimeout, except with arguments reversed. This + var CONSOLE_GROUP_SUPPORTED = ('console' in window) && console.group && + console.groupEnd; + + // A function that acts like setTimeout, except with arguments reversed. This // is far more readable within tests. function wait(duration, fn) { return setTimeout(fn, duration); @@ -206,7 +209,10 @@ }, startSuite: function (suite) { - console.group('Suite:', suite); + if (CONSOLE_GROUP_SUPPORTED) { + console.group('Suite:', suite); + } + if (this.currentFixtures && this.currentFixtures.parentNode) { this.currentFixtures.remove(); } @@ -218,7 +224,9 @@ }, endSuite: function (suite) { - console.groupEnd(); + if (CONSOLE_GROUP_SUPPORTED) { + console.groupEnd(); + } } }; From 4f4803ad27b5b1cbefe00ac806dd7e7e4f89b8f6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 19:54:36 -0500 Subject: [PATCH 434/502] Get the test runner working on Windows. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows, of course, doesn’t support process forking, so I have to use threads/queues on that awesome platform. Not ideal. --- test.new/runner.rb | 128 ++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 47 deletions(-) diff --git a/test.new/runner.rb b/test.new/runner.rb index 3afc664b6..658ab5f71 100644 --- a/test.new/runner.rb +++ b/test.new/runner.rb @@ -2,6 +2,7 @@ require 'sinatra/base' require 'cgi' require 'json' +require 'rbconfig' # A barebones runner for testing across multiple browsers quickly. # @@ -41,8 +42,21 @@ # rake test:run BROWSERS=chrome GREP=gsub # # (will run all tests whose names contain "gsub" in only Chrome) +# TODO: +# +# - Figure out a better way to manage the Sinatra app. Forking is the best +# way to separate its stdout and stderr, but that doesn't work on Windows. +# - Allow the user to specify paths to browser executables via a YAML file or +# something. Especially crucial on Windows. +# - Get the test server to report more stuff about failures so that the +# runner's output can be more specific about what failed and why. +# + module Runner + host = RbConfig::CONFIG['host'] + IS_WINDOWS = host.include?('mswin') || host.include?('mingw32') + module Browsers class Abstract @@ -58,7 +72,6 @@ def supported? end def host - require 'rbconfig' RbConfig::CONFIG['host'] end @@ -67,7 +80,7 @@ def macos? end def windows? - host.include?('mswin') + host.include?('mswin') || host.include?('mingw32') end def linux? @@ -76,20 +89,16 @@ def linux? def visit(url) if windows? - system("#{path} #{url}") + system(%Q["#{path}" "#{url}"]) elsif macos? system("open -g -a '#{path}' '#{url}'") elsif linux? - system("#{name} #{url}") + system(%Q["#{name}" "#{url}"]) end end def installed? - if macos? - installed = File.exists?(path) - else - true # TODO - end + path && File.exists?(path) end def name @@ -105,7 +114,7 @@ def path if macos? File.expand_path("/Applications/#{name}.app") else - @path + @path || nil end end end @@ -120,15 +129,6 @@ def supported? true end - def path - if windows? - Pathname.new('C:\Program Files').join( - 'Mozilla Firefox', 'firefox.exe') - else - super - end - end - end class IE < Abstract @@ -141,7 +141,11 @@ def supported? windows? end - def visit + def installed? + windows? + end + + def visit(url) ie = WIN32OLE.new('InternetExplorer.Application') ie.visible = true ie.Navigate(url) @@ -159,6 +163,10 @@ def supported? class Chrome < Abstract + def initialize(path=nil) + @path = path || 'C:\Program Files\Google\Chrome\Application\chrome.exe' + end + def name 'Google Chrome' end @@ -188,17 +196,22 @@ def initialize(path='C:\Program Files\Opera\Opera.exe') # page will make a JSONP call when all the tests have been run. class ResultsListener < Sinatra::Base set :port, 4568 + set :logging, false get '/results' do results = { - :tests => params[:tests].to_i, - :passes => params[:passes].to_i, - :failures => params[:failures].to_i, - :duration => params[:duration].to_f + "tests" => params[:tests].to_i, + "passes" => params[:passes].to_i, + "failures" => params[:failures].to_i, + "duration" => params[:duration].to_f } - pipe = Runner::write_pipe - pipe.write(JSON.dump(results) + "\n") + if IS_WINDOWS + Runner::queue.push(results) + else + pipe = Runner::write_pipe + pipe.write(JSON.dump(results) + "\n") + end # We don't even need to render anything; the test page doesn't care # about a response. @@ -209,6 +222,10 @@ class << self attr_accessor :read_pipe, :write_pipe + def queue + @queue ||= Queue.new + end + def run(browsers=nil, tests=nil, grep=nil) @browsers = browsers.nil? ? BROWSERS.keys : browsers.split(/\s*,\s*/).map(&:to_sym) @@ -223,25 +240,37 @@ def run(browsers=nil, tests=nil, grep=nil) @url << "&grep=#{CGI::escape(@grep)}" end - Runner::read_pipe, Runner::write_pipe = IO.pipe - - # Start up the Sinatra app to listen for test results, but do it in a - # fork because it sends some output to stdout and stderr that is - # irrelevant and annoying. - pid = fork do - Runner::read_pipe.close - STDOUT.reopen('/dev/null', 'w') - STDERR.reopen('/dev/null', 'w') - - ResultsListener.run! - end + # If we're on Linux/OS X, we want to fork off a process here, because + # it gives us better control over stdout/stderr. But Windows doesn't + # support forking, so we have to fall back to threads. + if IS_WINDOWS + thread_id = Thread.new do + # I don't see an easy way to turn off WEBrick's annoying logging, + # so let's just ignore it. + $stderr = StringIO.new + ResultsListener.run! + end + else + Runner::read_pipe, Runner::write_pipe = IO.pipe + + # Start up the Sinatra app to listen for test results, but do it in a + # fork because it sends some output to stdout and stderr that is + # irrelevant and annoying. + pid = fork do + Runner::read_pipe.close + STDOUT.reopen('/dev/null', 'w') + STDERR.reopen('/dev/null', 'w') + + ResultsListener.run! + end - Runner::write_pipe.close + Runner::write_pipe.close - # Make sure we clean up the forked process when we're done. - at_exit do - Process.kill(9, pid) - Process.wait(pid) + # Make sure we clean up the forked process when we're done. + at_exit do + Process.kill(9, pid) + Process.wait(pid) + end end trap('INT') { exit } @@ -263,11 +292,16 @@ def run(browsers=nil, tests=nil, grep=nil) browser.visit(@url) browser.teardown - message = Runner::read_pipe.gets - results = JSON.parse(message) - results_table[browser.name] = results - + if IS_WINDOWS + # On Windows we need to slow down a bit. I don't know why. + sleep 2 + results = Runner::queue.pop + else + message = Runner::read_pipe.gets + results = JSON.parse(message) + end puts "done." + results_table[browser.name] = results end puts "\n\n" From 562846de959ac1630a573e5ea268b48c553844c6 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 20:00:48 -0500 Subject: [PATCH 435/502] =?UTF-8?q?For=20`rake=20dist`,=20grab=20the=20sub?= =?UTF-8?q?module=20for=20whatever=20selector=20engine=20was=20specified,?= =?UTF-8?q?=20even=20if=20it=E2=80=99s=20the=20default.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the thing I forgot to do when I removed our repo’s copy of Sizzle. That version of Sizzle _did_ get used if you tried to `rake dist` but you didn’t have the Sizzle submodule initialized. Considering that we’d have fetched the submodule automatically anyway, it’s not clear how much of a victory this was for user-friendliness. --- Rakefile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index e8865a1ac..c7133ddb4 100755 --- a/Rakefile +++ b/Rakefile @@ -170,18 +170,12 @@ EOF def self.get_selector_engine(name) return if !name - # If the submodule exists, we should use it, even if we're using the - # default engine; the user might have fetched it manually, and thus would - # want to build a distributable with the most recent version of that - # engine. + # If the submodule exists, we should use it. submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) return submodule_path if name === "legacy_selector" - # If it doesn't exist, we should fetch it, _unless_ it's the default - # engine. We've already got a known version of the default engine in our - # load path. - return if name == DEFAULT_SELECTOR_ENGINE + # If it doesn't exist, we should fetch it. get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" From 2bf2aed903e340885a284aebe3c655e53fb297b4 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 14 May 2014 20:00:48 -0500 Subject: [PATCH 436/502] =?UTF-8?q?For=20`rake=20dist`,=20grab=20the=20sub?= =?UTF-8?q?module=20for=20whatever=20selector=20engine=20was=20specified,?= =?UTF-8?q?=20even=20if=20it=E2=80=99s=20the=20default.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the thing I forgot to do when I removed our repo’s copy of Sizzle. That version of Sizzle _did_ get used if you tried to `rake dist` but you didn’t have the Sizzle submodule initialized. Considering that we’d have fetched the submodule automatically anyway, it’s not clear how much of a victory this was for user-friendliness. --- Rakefile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index 0df098ec5..6a6165b64 100755 --- a/Rakefile +++ b/Rakefile @@ -160,18 +160,12 @@ EOF def self.get_selector_engine(name) return if !name - # If the submodule exists, we should use it, even if we're using the - # default engine; the user might have fetched it manually, and thus would - # want to build a distributable with the most recent version of that - # engine. + # If the submodule exists, we should use it. submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) return submodule_path if name === "legacy_selector" - # If it doesn't exist, we should fetch it, _unless_ it's the default - # engine. We've already got a known version of the default engine in our - # load path. - return if name == DEFAULT_SELECTOR_ENGINE + # If it doesn't exist, we should fetch it. get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" From 0de22eaaa7b9d12c7c6fc0e595d6a8732b048381 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 17 May 2014 18:03:03 -0500 Subject: [PATCH 437/502] Make test server accessible outside of localhost and set `X-UA-Compatible` automatically. --- test.new/server.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test.new/server.rb b/test.new/server.rb index cf07865e1..a3373ed40 100644 --- a/test.new/server.rb +++ b/test.new/server.rb @@ -11,6 +11,10 @@ class UnitTests < Sinatra::Application set :root, PWD set :public_folder, PWD.join('static') + # By default, the server is only reachable locally. We change this so that + # we can start the server on one machine and then run tests from another. + set :bind, '0.0.0.0' + PATH_TO_PROTOTYPE = PWD.join('..', 'dist', 'prototype.js') unless PATH_TO_PROTOTYPE.file? @@ -19,7 +23,6 @@ class UnitTests < Sinatra::Application PATH_TO_TEST_JS = PWD.join('tests') - SUITES = [] PATH_TO_TEST_JS.each_entry do |e| @@ -37,6 +40,10 @@ class UnitTests < Sinatra::Application SUITES_WITH_VIEWS << basename end + after do + headers('X-UA-Compatible' => 'IE=edge') + end + def self.get_or_post(url, &block) get(url, &block) From 477b61acb5f6773f7c47b1694c51de29c66abc10 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 17 May 2014 18:03:55 -0500 Subject: [PATCH 438/502] Fix some IE6 test failures. --- test.new/tests/dom.test.js | 12 ++++++------ test.new/tests/form.test.js | 5 ++++- test.new/tests/layout.test.js | 7 ++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/test.new/tests/dom.test.js b/test.new/tests/dom.test.js index 6a6007e9e..5267032c8 100644 --- a/test.new/tests/dom.test.js +++ b/test.new/tests/dom.test.js @@ -1042,11 +1042,11 @@ suite('DOM', function () { if (!STANDARD_CSS_OPACITY_SUPPORTED) { // Run these tests only on older versions of IE. IE9 and 10 dropped // support for filters and therefore fail these tests. - assert.equal('alpha(opacity=30)', $('op1').getStyle('filter')); - assert.equal('progid:DXImageTransform.Microsoft.Blur(strength=10)alpha(opacity=30)', $('op2').getStyle('filter')); + assert.equal('alpha(opacity=30)', $('op1').getStyle('filter').strip()); + assert.equal('progid:DXImageTransform.Microsoft.Blur(strength=10) alpha(opacity=30)', $('op2').getStyle('filter')); $('op2').setStyle({opacity:''}); - assert.equal('progid:DXImageTransform.Microsoft.Blur(strength=10)', $('op2').getStyle('filter')); - assert.equal('alpha(opacity=0)', $('op3').getStyle('filter')); + assert.equal('progid:DXImageTransform.Microsoft.Blur(strength=10)', $('op2').getStyle('filter').strip()); + assert.equal('alpha(opacity=0)', $('op3').getStyle('filter').strip()); assert.equal(0.3, $('op4-ie').getStyle('opacity')); } @@ -1191,7 +1191,7 @@ suite('DOM', function () { checkedCheckbox = $('write_attribute_checked_checkbox'); assert( checkbox. writeAttribute('checked'). checked); assert( checkbox. writeAttribute('checked'). hasAttribute('checked')); - assert.equal('checked', checkbox.writeAttribute('checked').getAttribute('checked')); + assert.equal('checked', checkbox.writeAttribute('checked').readAttribute('checked')); assert(!checkbox. writeAttribute('checked'). hasAttribute('undefined')); assert( checkbox. writeAttribute('checked', true). checked); assert( checkbox. writeAttribute('checked', true). hasAttribute('checked')); @@ -1275,7 +1275,7 @@ suite('DOM', function () { assert.equal(tag, element.tagName.toLowerCase()); assert.equal(element, document.body.lastChild); assert.equal(id, element.id); - element.remove(); + Element.remove(element); }, this); diff --git a/test.new/tests/form.test.js b/test.new/tests/form.test.js index 28c2f39cf..684360e53 100644 --- a/test.new/tests/form.test.js +++ b/test.new/tests/form.test.js @@ -4,7 +4,10 @@ suite('Form', function () { this.name = 'form'; setup(function () { - $$('div.form-tests form').invoke('reset'); + // $$('div.form-tests form').invoke('reset'); + $$('div.form-tests form').each(function (f) { + Form.reset(f); + }); // for some reason, hidden value does not reset $('form-test-bigform')['tf_hidden'].value = ''; }); diff --git a/test.new/tests/layout.test.js b/test.new/tests/layout.test.js index f182051b2..2f8b3babf 100644 --- a/test.new/tests/layout.test.js +++ b/test.new/tests/layout.test.js @@ -390,7 +390,12 @@ suite("Layout",function(){ test('#getDimensions', function () { var original = document.viewport.getDimensions(); - window.resizeTo(800, 600); + try { + window.resizeTo(800, 600); + } catch (e) { + info("Can't resize."); + return; + } wait(1000, function() { var before = document.viewport.getDimensions(); From 3b67befb8222eb58edceb2638b5c2d714b7f6033 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 17 May 2014 18:04:07 -0500 Subject: [PATCH 439/502] Tweak the test output CSS so it looks slightly less awful in IE6. --- test.new/views/layout.erb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test.new/views/layout.erb b/test.new/views/layout.erb index 42fe4ea5f..45676905e 100644 --- a/test.new/views/layout.erb +++ b/test.new/views/layout.erb @@ -29,6 +29,11 @@ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } + + #mocha .suite h1, + #mocha li.test { + clear: both; + } From 6de3ea468ea09709fe7605c686949430e5912881 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 17 May 2014 18:06:00 -0500 Subject: [PATCH 440/502] Change the Sizzle module to pull sizzle.js from /dist rather than /src. [close #194] --- vendor/sizzle/selector_engine.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index fd44958d6..1e62186db 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,9 +1,9 @@ Prototype._original_property = window.Sizzle; -//= require "repository/src/sizzle" +//= require "repository/dist/sizzle" ;(function(engine) { var extendElements = Prototype.Selector.extendElements; - + function select(selector, scope) { return extendElements(engine(selector, scope || document)); } @@ -11,7 +11,7 @@ Prototype._original_property = window.Sizzle; function match(element, selector) { return engine.matches(selector, [element]).length == 1; } - + Prototype.Selector.engine = engine; Prototype.Selector.select = select; Prototype.Selector.match = match; From fa2fcdb20a424ae528a62cf1d10f0c704e1142fe Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 May 2014 20:40:23 -0500 Subject: [PATCH 441/502] Move the fixtures out of the static folder. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way we can ensure they’re not cached. This fixed a failing test in IE 6-7. --- test.new/{static => }/fixtures/content.html | 0 test.new/{static => }/fixtures/data.json | 0 test.new/{static => }/fixtures/empty.html | 0 test.new/{static => }/fixtures/hello.js | 0 test.new/server.rb | 28 +++++++++++++++++++-- 5 files changed, 26 insertions(+), 2 deletions(-) rename test.new/{static => }/fixtures/content.html (100%) rename test.new/{static => }/fixtures/data.json (100%) rename test.new/{static => }/fixtures/empty.html (100%) rename test.new/{static => }/fixtures/hello.js (100%) diff --git a/test.new/static/fixtures/content.html b/test.new/fixtures/content.html similarity index 100% rename from test.new/static/fixtures/content.html rename to test.new/fixtures/content.html diff --git a/test.new/static/fixtures/data.json b/test.new/fixtures/data.json similarity index 100% rename from test.new/static/fixtures/data.json rename to test.new/fixtures/data.json diff --git a/test.new/static/fixtures/empty.html b/test.new/fixtures/empty.html similarity index 100% rename from test.new/static/fixtures/empty.html rename to test.new/fixtures/empty.html diff --git a/test.new/static/fixtures/hello.js b/test.new/fixtures/hello.js similarity index 100% rename from test.new/static/fixtures/hello.js rename to test.new/fixtures/hello.js diff --git a/test.new/server.rb b/test.new/server.rb index a3373ed40..6df88d9b9 100644 --- a/test.new/server.rb +++ b/test.new/server.rb @@ -8,6 +8,8 @@ class UnitTests < Sinatra::Application PWD = Pathname.new( File.expand_path( File.dirname(__FILE__) ) ) + UNIQUE_ASSET_STRING = Time.new.to_i + set :root, PWD set :public_folder, PWD.join('static') @@ -21,7 +23,8 @@ class UnitTests < Sinatra::Application raise "You must run `rake dist` before starting the server." end - PATH_TO_TEST_JS = PWD.join('tests') + PATH_TO_TEST_JS = PWD.join('tests') + PATH_TO_FIXTURES = PWD.join('fixtures') SUITES = [] @@ -41,7 +44,12 @@ class UnitTests < Sinatra::Application end after do - headers('X-UA-Compatible' => 'IE=edge') + headers({ + 'X-UA-Compatible' => 'IE=edge', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', + 'Pragma' => 'no-cache', + 'Expires' => '0' + }) end @@ -50,9 +58,14 @@ def self.get_or_post(url, &block) post(url, &block) end + get '/test' do + redirect to('/test/') + end + get '/test/:names?' do names = params[:names] @suites = names.nil? ? SUITES : names.split(/,/).uniq + @unique_asset_string = UNIQUE_ASSET_STRING.to_s erb :tests, :locals => { :suites => @suites } end @@ -61,6 +74,12 @@ def self.get_or_post(url, &block) send_file PATH_TO_PROTOTYPE end + + # We don't put either of these in the /static directory because + # (a) they should be more prominent in the directory structure; + # (b) they should never, ever get cached, and we want to enforce that + # aggressively. + get '/js/tests/:filename' do filename = params[:filename] path = PATH_TO_TEST_JS.join(filename) @@ -72,6 +91,11 @@ def self.get_or_post(url, &block) end end + get '/fixtures/:filename' do + filename = params[:filename] + send_file PATH_TO_FIXTURES.join(filename) + end + # Routes for Ajax tests From 99fce51b51e4bb2c1f7e71f4db82558fd8504069 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 May 2014 20:45:34 -0500 Subject: [PATCH 442/502] Fix some async tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These weren’t being handled as async tests even though they do async stuff. Any failures in these tests were being thrown but not caught; in most browsers this just displays an error, but in IE 6-7 it caused everything to get out of whack. Subsequent tests would run at the wrong time and would fail because they didn’t see the HTML fixtures they were expecting. --- test.new/tests/function.test.js | 6 ++++-- test.new/tests/layout.test.js | 11 ++++++++--- test.new/tests/periodical_executer.test.js | 3 ++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/test.new/tests/function.test.js b/test.new/tests/function.test.js index a3b81a632..161636df5 100644 --- a/test.new/tests/function.test.js +++ b/test.new/tests/function.test.js @@ -111,7 +111,7 @@ suite('Function', function () { assert.strictEqual(split, split.curry()); }); - test('#delay', function () { + test('#delay', function (done) { window.delayed = undefined; var delayedFunction = function() { window.delayed = true; }; var delayedFunctionWithArgs = function() { window.delayedWithArgs = $A(arguments).join(' '); }; @@ -121,6 +121,7 @@ suite('Function', function () { wait(1000, function() { assert(window.delayed); assert.equal('hello world', window.delayedWithArgs); + done(); }); }); @@ -146,7 +147,7 @@ suite('Function', function () { String.prototype.capitalize = temp; }); - test('#defer', function () { + test('#defer', function (done) { window.deferred = undefined; var deferredFunction = function() { window.deferred = true; }; deferredFunction.defer(); @@ -180,6 +181,7 @@ suite('Function', function () { wait(50, function() { assert(window.deferBoundProperlyOnString); + done(); }); }); }); diff --git a/test.new/tests/layout.test.js b/test.new/tests/layout.test.js index 2f8b3babf..4a1855ad8 100644 --- a/test.new/tests/layout.test.js +++ b/test.new/tests/layout.test.js @@ -387,7 +387,8 @@ suite("Layout",function(){ suite('document.viewport', function () { - test('#getDimensions', function () { + test('#getDimensions', function (done) { + this.timeout(5000); var original = document.viewport.getDimensions(); try { @@ -404,13 +405,14 @@ suite("Layout",function(){ window.resizeBy(50, 50); wait(1000, function() { - var after = document.viewport.getDimensions(); + var after = document.viewport.getDimensions(); // Assume that JavaScript window resizing is disabled if before width // and after width are the same. if (before.width === after.width) { RESIZE_DISABLED = true; info("SKIPPING REMAINING TESTS (JavaScript window resizing disabled)"); + done(); return; } @@ -429,6 +431,7 @@ suite("Layout",function(){ original.width + delta.width, original.height + delta.height ); + done(); }); }); }); @@ -449,7 +452,8 @@ suite("Layout",function(){ }); }); - test('#getScrollOffsets', function () { + test('#getScrollOffsets', function (done) { + this.timeout(5000); var original = document.viewport.getDimensions(); window.scrollTo(0, 0); @@ -479,6 +483,7 @@ suite("Layout",function(){ original.width + delta.width, original.height + delta.height ); + done(); }); }); }); diff --git a/test.new/tests/periodical_executer.test.js b/test.new/tests/periodical_executer.test.js index f97d73582..23672eb88 100644 --- a/test.new/tests/periodical_executer.test.js +++ b/test.new/tests/periodical_executer.test.js @@ -2,7 +2,7 @@ suite('PeriodicalExecuter', function () { this.name = 'periodical_executer'; - test('#stop', function () { + test('#stop', function (done) { var peEventCount = 0; function peEventFired(pe) { if (++peEventCount > 2) pe.stop(); @@ -13,6 +13,7 @@ suite('PeriodicalExecuter', function () { wait(600, function() { assert.equal(3, peEventCount); + done(); }); }); From 6a992259424e028616dc83147d18f0160219079d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 May 2014 20:46:06 -0500 Subject: [PATCH 443/502] Add a numeric query string to all the assets we serve out of /static just to be safe. --- test.new/views/layout.erb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test.new/views/layout.erb b/test.new/views/layout.erb index 45676905e..74eebc6ad 100644 --- a/test.new/views/layout.erb +++ b/test.new/views/layout.erb @@ -36,10 +36,10 @@ } - - - - + + + + <% @suites.each do |suite| %> - + <% end %> From 560f3496836729518de104b9dce8d77e9b725667 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 May 2014 20:46:33 -0500 Subject: [PATCH 444/502] Unify our feature detection for console.log/group. --- test.new/static/js/test_helpers.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test.new/static/js/test_helpers.js b/test.new/static/js/test_helpers.js index 441c0b279..f63573b27 100644 --- a/test.new/static/js/test_helpers.js +++ b/test.new/static/js/test_helpers.js @@ -1,19 +1,30 @@ (function () { + var CONSOLE_LOG_SUPPORTED = ('console' in window) && console.log; + var CONSOLE_GROUP_SUPPORTED = ('console' in window) && console.group && + console.groupEnd; + var CONSOLE_LOG_APPLY = true; + function info() { - console.log.apply(console, arguments); + if (CONSOLE_LOG_APPLY) { + console.log.apply(console, arguments); + } else { + console.log(arguments); + } } - // Fall back when we don't have the console. - // TODO: Find a different way of logging in old IEs. - if (!('console' in window) || !console.log) { - info = function () {}; + if (!CONSOLE_LOG_SUPPORTED) { + info = Prototype.emptyFunction; + } else { + try { + console.log.apply(console, [""]); + } catch (e) { + CONSOLE_LOG_APPLY = false; + } } - window.info = info; - var CONSOLE_GROUP_SUPPORTED = ('console' in window) && console.group && - console.groupEnd; + window.info = info; // A function that acts like setTimeout, except with arguments reversed. This // is far more readable within tests. @@ -193,6 +204,7 @@ // tests run, then detach them all from the document. Then, before a suite // runs, its fixtures are reattached, then removed again before the next // suite runs. + // window.Test = { setup: function () { var body = $(document.body); @@ -211,6 +223,8 @@ startSuite: function (suite) { if (CONSOLE_GROUP_SUPPORTED) { console.group('Suite:', suite); + } else if (CONSOLE_LOG_SUPPORTED) { + console.log('Suite:', suite); } if (this.currentFixtures && this.currentFixtures.parentNode) { From 62a393a4a7b94bd8ae1a517974344d8ea40ff843 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 18 May 2014 23:04:41 -0500 Subject: [PATCH 445/502] Rewrite the `wait` function so that it gives useful failure messages in IE 6-7. --- test.new/static/js/test_helpers.js | 31 +++++++++++++++++++--- test.new/tests/dom.test.js | 12 ++++----- test.new/tests/form.test.js | 6 ++--- test.new/tests/function.test.js | 10 +++---- test.new/tests/layout.test.js | 21 ++++++++------- test.new/tests/periodical_executer.test.js | 2 +- 6 files changed, 54 insertions(+), 28 deletions(-) diff --git a/test.new/static/js/test_helpers.js b/test.new/static/js/test_helpers.js index f63573b27..4c981794e 100644 --- a/test.new/static/js/test_helpers.js +++ b/test.new/static/js/test_helpers.js @@ -1,6 +1,8 @@ (function () { + var IS_IE6 = (navigator.userAgent.indexOf('MSIE 6.0') > -1); + var IS_IE7 = (navigator.userAgent.indexOf('MSIE 7.0') > -1); var CONSOLE_LOG_SUPPORTED = ('console' in window) && console.log; var CONSOLE_GROUP_SUPPORTED = ('console' in window) && console.group && console.groupEnd; @@ -26,10 +28,33 @@ window.info = info; - // A function that acts like setTimeout, except with arguments reversed. This + // A function that acts like setTimeout, except with arguments reversed. This // is far more readable within tests. - function wait(duration, fn) { - return setTimeout(fn, duration); + function wait(duration, done, fn) { + var handler = function () { + try { + fn(); + } catch (e) { + // In IE6, manually throwing errors doesn't trigger window.onerror. + // Instead, we'll pass an actual error to the `done` callback. + if (IS_IE6) { + if (Object.isFunction(done)) { + return done(new Error(e.message)); + } + } + + // In IE7, window.onerror will get triggered, but with a generic + // error message. Instead we need to throw an actual Error object + // because it does not grok this whole custom error thing. + if (IS_IE7) { + throw new Error(e.message); + } + + // In other browsers, we can just re-throw it and be fine. + throw e; + } + }; + return setTimeout(handler, duration); } window.wait = wait; diff --git a/test.new/tests/dom.test.js b/test.new/tests/dom.test.js index 5267032c8..51663400d 100644 --- a/test.new/tests/dom.test.js +++ b/test.new/tests/dom.test.js @@ -429,7 +429,7 @@ suite('DOM', function () { $('testdiv').update('hello from div! From 9dfeb5074498c1276dce778b765bbe9bb11c7dd1 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 19 May 2014 10:15:47 -0500 Subject: [PATCH 450/502] Fix potential crashes when running the entire test suite in IE6-7. --- test.new/static/js/test_helpers.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test.new/static/js/test_helpers.js b/test.new/static/js/test_helpers.js index 068e0321c..ba2a055d6 100644 --- a/test.new/static/js/test_helpers.js +++ b/test.new/static/js/test_helpers.js @@ -240,11 +240,13 @@ if (CONSOLE_GROUP_SUPPORTED) { console.group('Suite:', suite); } else if (CONSOLE_LOG_SUPPORTED) { - console.log('Suite:', suite); + console.log('Suite: ', suite); } - if (this.currentFixtures && this.currentFixtures.parentNode) { - this.currentFixtures.remove(); + // Calling `remove` on this node has been known to crash the tests in + // IE6-7. + if (this.currentFixtures) { + $('current_fixtures').update(); } if (this.fixtures[suite]) { From eb9fdc9b84eef6160b8cd3f85912839831109eb7 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 19 May 2014 10:15:56 -0500 Subject: [PATCH 451/502] Fix test timeout. --- test.new/tests/layout.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.new/tests/layout.test.js b/test.new/tests/layout.test.js index 65ddc6854..a9a336938 100644 --- a/test.new/tests/layout.test.js +++ b/test.new/tests/layout.test.js @@ -388,7 +388,7 @@ suite("Layout",function(){ suite('document.viewport', function () { test('#getDimensions', function (done) { - this.timeout(60000); + this.timeout(5000); var original = document.viewport.getDimensions(); try { From e3c20313cbdc1327b376a8fb52bb7219c506442e Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Mon, 19 May 2014 16:45:15 -0500 Subject: [PATCH 452/502] First attempt at integrating headless PhantomJS tests. --- Rakefile | 17 ++ test.new/phantomjs/core-extensions.js | 70 +++++++ test.new/phantomjs/mocha-phantomjs.js | 260 ++++++++++++++++++++++++++ test.new/views/layout.erb | 6 +- 4 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 test.new/phantomjs/core-extensions.js create mode 100644 test.new/phantomjs/mocha-phantomjs.js diff --git a/Rakefile b/Rakefile index c7133ddb4..a34151813 100755 --- a/Rakefile +++ b/Rakefile @@ -109,6 +109,15 @@ EOF end end + def self.require_phantomjs + success = system("phantomjs -v > /dev/null 2>&1") + if !success + puts "\nYou need phantomjs installed to run this task. Find out how at:" + puts " http://phantomjs.org/download.html" + exit + end + end + def self.syntax_highlighter if ENV['SYNTAX_HIGHLIGHTER'] highlighter = ENV['SYNTAX_HIGHLIGHTER'].to_sym @@ -374,6 +383,14 @@ namespace :test_new do Runner::run(browsers, tests, grep) end + task :phantom => [:require] do + PrototypeHelper.require_phantomjs + tests, grep = ENV['TESTS'], ENV['GREP'] + url = "http://127.0.0.1:4567/test/#{tests}" + url << "?grep=#{grep}" if grep + system(%Q[phantomjs ./test.new/phantomjs/mocha-phantomjs.js "#{url}"]) + end + end namespace :caja do diff --git a/test.new/phantomjs/core-extensions.js b/test.new/phantomjs/core-extensions.js new file mode 100644 index 000000000..744ba99cd --- /dev/null +++ b/test.new/phantomjs/core-extensions.js @@ -0,0 +1,70 @@ +/*! + * PhantomJS Runners for Mocha + * https://github.com/metaskills/mocha-phantomjs/ + * + * Copyright (c) 2012 Ken Collins + * Released under the MIT license + * http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE + * + */ + +(function(){ + + // A shim for non ES5 supporting browsers, like PhantomJS. Lovingly inspired by: + // http://www.angrycoding.com/2011/09/to-bind-or-not-to-bind-that-is-in.html + if (!('bind' in Function.prototype)) { + Function.prototype.bind = function() { + var funcObj = this; + var extraArgs = Array.prototype.slice.call(arguments); + var thisObj = extraArgs.shift(); + return function() { + return funcObj.apply(thisObj, extraArgs.concat(Array.prototype.slice.call(arguments))); + }; + }; + } + + // Mocha needs a process.stdout.write in order to change the cursor position. + Mocha.process = Mocha.process || {}; + Mocha.process.stdout = Mocha.process.stdout || process.stdout; + Mocha.process.stdout.write = function(s) { window.callPhantom({"Mocha.process.stdout.write":s}); } + + // Mocha needs the formating feature of console.log so copy node's format function and + // monkey-patch it into place. This code is copied from node's, links copyright applies. + // https://github.com/joyent/node/blob/master/lib/util.js + console.format = function(f) { + if (typeof f !== 'string') { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(JSON.stringify(arguments[i])); + } + return objects.join(' '); + } + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(/%[sdj%]/g, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': return JSON.stringify(args[i++]); + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (x === null || typeof x !== 'object') { + str += ' ' + x; + } else { + str += ' ' + JSON.stringify(x); + } + } + return str; + }; + var origError = console.error; + console.error = function(){ origError.call(console, console.format.apply(console, arguments)); }; + var origLog = console.log; + console.log = function(){ origLog.call(console, console.format.apply(console, arguments)); }; + +})(); diff --git a/test.new/phantomjs/mocha-phantomjs.js b/test.new/phantomjs/mocha-phantomjs.js new file mode 100644 index 000000000..51a9877d3 --- /dev/null +++ b/test.new/phantomjs/mocha-phantomjs.js @@ -0,0 +1,260 @@ +/*! + * PhantomJS Runners for Mocha + * https://github.com/metaskills/mocha-phantomjs/ + * + * Copyright (c) 2012 Ken Collins + * Released under the MIT license + * http://github.com/metaskills/mocha-phantomjs/blob/master/MIT-LICENSE + * + */ + +// Generated by CoffeeScript 1.7.1 +(function() { + var Reporter, USAGE, config, mocha, reporter, system, webpage, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + system = require('system'); + + webpage = require('webpage'); + + USAGE = "Usage: phantomjs mocha-phantomjs.coffee URL REPORTER [CONFIG]"; + + Reporter = (function() { + function Reporter(reporter, config) { + this.reporter = reporter; + this.config = config; + this.checkStarted = __bind(this.checkStarted, this); + this.waitForRunMocha = __bind(this.waitForRunMocha, this); + this.waitForInitMocha = __bind(this.waitForInitMocha, this); + this.waitForMocha = __bind(this.waitForMocha, this); + this.url = system.args[1]; + this.columns = parseInt(system.env.COLUMNS || 75) * .75 | 0; + this.mochaStarted = false; + this.mochaStartWait = this.config.timeout || 6000; + this.startTime = Date.now(); + if (!this.url) { + this.fail(USAGE); + } + } + + Reporter.prototype.run = function() { + this.initPage(); + return this.loadPage(); + }; + + Reporter.prototype.customizeMocha = function(options) { + return Mocha.reporters.Base.window.width = options.columns; + }; + + Reporter.prototype.customizeOptions = function() { + return { + columns: this.columns + }; + }; + + Reporter.prototype.fail = function(msg, errno) { + if (msg) { + console.log(msg); + } + return phantom.exit(errno || 1); + }; + + Reporter.prototype.finish = function() { + return phantom.exit(this.page.evaluate(function() { + return mochaPhantomJS.failures; + })); + }; + + Reporter.prototype.initPage = function() { + var cookie, _i, _len, _ref; + this.page = webpage.create({ + settings: this.config.settings + }); + if (this.config.headers) { + this.page.customHeaders = this.config.headers; + } + _ref = this.config.cookies || []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cookie = _ref[_i]; + this.page.addCookie(cookie); + } + if (this.config.viewportSize) { + this.page.viewportSize = this.config.viewportSize; + } + this.page.onConsoleMessage = function(msg) { + return system.stdout.writeLine(msg); + }; + this.page.onError = (function(_this) { + return function(msg, traces) { + var file, index, line, _j, _len1, _ref1; + if (_this.page.evaluate(function() { + return window.onerror != null; + })) { + return; + } + for (index = _j = 0, _len1 = traces.length; _j < _len1; index = ++_j) { + _ref1 = traces[index], line = _ref1.line, file = _ref1.file; + traces[index] = " " + file + ":" + line; + } + return _this.fail("" + msg + "\n\n" + (traces.join('\n'))); + }; + })(this); + return this.page.onInitialized = (function(_this) { + return function() { + return _this.page.evaluate(function(env) { + return window.mochaPhantomJS = { + env: env, + failures: 0, + ended: false, + started: false, + run: function() { + mochaPhantomJS.started = true; + return window.callPhantom({ + 'mochaPhantomJS.run': true + }); + } + }; + }, system.env); + }; + })(this); + }; + + Reporter.prototype.loadPage = function() { + this.page.open(this.url); + this.page.onLoadFinished = (function(_this) { + return function(status) { + _this.page.onLoadFinished = function() {}; + if (status !== 'success') { + _this.onLoadFailed(); + } + return _this.waitForInitMocha(); + }; + })(this); + return this.page.onCallback = (function(_this) { + return function(data) { + if (data.hasOwnProperty('Mocha.process.stdout.write')) { + system.stdout.write(data['Mocha.process.stdout.write']); + } else if (data.hasOwnProperty('mochaPhantomJS.run')) { + if (_this.injectJS()) { + _this.waitForRunMocha(); + } + } + return true; + }; + })(this); + }; + + Reporter.prototype.onLoadFailed = function() { + return this.fail("Failed to load the page. Check the url: " + this.url); + }; + + Reporter.prototype.injectJS = function() { + if (this.page.evaluate(function() { + return window.mocha != null; + })) { + this.page.injectJs('core-extensions.js'); + this.page.evaluate(this.customizeMocha, this.customizeOptions()); + return true; + } else { + this.fail("Failed to find mocha on the page."); + return false; + } + }; + + Reporter.prototype.runMocha = function() { + if (this.config.useColors === false) { + this.page.evaluate(function() { + return Mocha.reporters.Base.useColors = false; + }); + } + this.page.evaluate(this.runner, this.reporter); + this.mochaStarted = this.page.evaluate(function() { + return mochaPhantomJS.runner || false; + }); + if (this.mochaStarted) { + this.mochaRunAt = new Date().getTime(); + return this.waitForMocha(); + } else { + return this.fail("Failed to start mocha."); + } + }; + + Reporter.prototype.waitForMocha = function() { + var ended; + ended = this.page.evaluate(function() { + return mochaPhantomJS.ended; + }); + if (ended) { + return this.finish(); + } else { + return setTimeout(this.waitForMocha, 100); + } + }; + + Reporter.prototype.waitForInitMocha = function() { + if (!this.checkStarted()) { + return setTimeout(this.waitForInitMocha, 100); + } + }; + + Reporter.prototype.waitForRunMocha = function() { + if (this.checkStarted()) { + return this.runMocha(); + } else { + return setTimeout(this.waitForRunMocha, 100); + } + }; + + Reporter.prototype.checkStarted = function() { + var started; + started = this.page.evaluate(function() { + return mochaPhantomJS.started; + }); + if (!started && this.mochaStartWait && this.startTime + this.mochaStartWait < Date.now()) { + this.fail("Failed to start mocha: Init timeout", 255); + } + return started; + }; + + Reporter.prototype.runner = function(reporter) { + var cleanup, error, _ref, _ref1; + try { + mocha.setup({ + reporter: reporter + }); + mochaPhantomJS.runner = mocha.run(); + if (mochaPhantomJS.runner) { + cleanup = function() { + mochaPhantomJS.failures = mochaPhantomJS.runner.failures; + return mochaPhantomJS.ended = true; + }; + if ((_ref = mochaPhantomJS.runner) != null ? (_ref1 = _ref.stats) != null ? _ref1.end : void 0 : void 0) { + return cleanup(); + } else { + return mochaPhantomJS.runner.on('end', cleanup); + } + } + } catch (_error) { + error = _error; + return false; + } + }; + + return Reporter; + + })(); + + if (phantom.version.major !== 1 || phantom.version.minor < 9) { + console.log('mocha-phantomjs requires PhantomJS > 1.9.1'); + phantom.exit(-1); + } + + reporter = system.args[2] || 'spec'; + + config = JSON.parse(system.args[3] || '{}'); + + mocha = new Reporter(reporter, config); + + mocha.run(); + +}).call(this); diff --git a/test.new/views/layout.erb b/test.new/views/layout.erb index 0153067a5..8c55944ff 100644 --- a/test.new/views/layout.erb +++ b/test.new/views/layout.erb @@ -87,7 +87,11 @@ " - - str.evalScripts.bind(str).defer(); - - this.wait(50, function() { - this.assert(window.deferBoundProperlyOnString); - }); - - }); - - }); - }); - - - }, - - testFunctionMethodize: function() { - var Foo = { bar: function(baz) { return baz } }; - var baz = { quux: Foo.bar.methodize() }; - - this.assertEqual(Foo.bar.methodize(), baz.quux); - this.assertEqual(baz, Foo.bar(baz)); - this.assertEqual(baz, baz.quux()); - }, - - testBindAsEventListener: function() { - for( var i = 0; i < 10; ++i ){ - var div = document.createElement('div'); - div.setAttribute('id','test-'+i); - document.body.appendChild(div); - var tobj = new TestObj(); - var eventTest = { test: true }; - var call = tobj.assertingEventHandler.bindAsEventListener(tobj, - this.assertEqual.bind(this, eventTest), - this.assertEqual.bind(this, arg1), - this.assertEqual.bind(this, arg2), - this.assertEqual.bind(this, arg3), arg1, arg2, arg3 ); - call(eventTest); - } - } -}); \ No newline at end of file diff --git a/test/unit.old/hash_test.js b/test/unit.old/hash_test.js deleted file mode 100644 index ed779f2d5..000000000 --- a/test/unit.old/hash_test.js +++ /dev/null @@ -1,197 +0,0 @@ -new Test.Unit.Runner({ - testSet: function() { - var h = $H({a: 'A'}) - - this.assertEqual('B', h.set('b', 'B')); - this.assertHashEqual({a: 'A', b: 'B'}, h); - - this.assertUndefined(h.set('c')); - this.assertHashEqual({a: 'A', b: 'B', c: undefined}, h); - }, - - testGet: function() { - var h = $H({a: 'A'}); - this.assertEqual('A', h.get('a')); - this.assertUndefined(h.a); - this.assertUndefined($H({}).get('a')); - - this.assertUndefined($H({}).get('toString')); - this.assertUndefined($H({}).get('constructor')); - }, - - testUnset: function() { - var hash = $H(Fixtures.many); - this.assertEqual('B', hash.unset('b')); - this.assertHashEqual({a:'A', c: 'C', d:'D#'}, hash); - this.assertUndefined(hash.unset('z')); - this.assertHashEqual({a:'A', c: 'C', d:'D#'}, hash); - // not equivalent to Hash#remove - this.assertEqual('A', hash.unset('a', 'c')); - this.assertHashEqual({c: 'C', d:'D#'}, hash); - }, - - testToObject: function() { - var hash = $H(Fixtures.many), object = hash.toObject(); - this.assertInstanceOf(Object, object); - this.assertHashEqual(Fixtures.many, object); - this.assertNotIdentical(Fixtures.many, object); - hash.set('foo', 'bar'); - this.assertHashNotEqual(object, hash.toObject()); - }, - - testConstruct: function() { - var object = Object.clone(Fixtures.one); - var h = new Hash(object), h2 = $H(object); - this.assertInstanceOf(Hash, h); - this.assertInstanceOf(Hash, h2); - - this.assertHashEqual({}, new Hash()); - this.assertHashEqual(object, h); - this.assertHashEqual(object, h2); - - h.set('foo', 'bar'); - this.assertHashNotEqual(object, h); - - var clone = $H(h); - this.assertInstanceOf(Hash, clone); - this.assertHashEqual(h, clone); - h.set('foo', 'foo'); - this.assertHashNotEqual(h, clone); - this.assertIdentical($H, Hash.from); - }, - - testKeys: function() { - this.assertEnumEqual([], $H({}).keys()); - this.assertEnumEqual(['a'], $H(Fixtures.one).keys()); - this.assertEnumEqual($w('a b c d'), $H(Fixtures.many).keys().sort()); - this.assertEnumEqual($w('plus quad'), $H(Fixtures.functions).keys().sort()); - }, - - testValues: function() { - this.assertEnumEqual([], $H({}).values()); - this.assertEnumEqual(['A#'], $H(Fixtures.one).values()); - this.assertEnumEqual($w('A B C D#'), $H(Fixtures.many).values().sort()); - this.assertEnumEqual($w('function function'), - $H(Fixtures.functions).values().map(function(i){ return typeof i })); - this.assertEqual(9, $H(Fixtures.functions).get('quad')(3)); - this.assertEqual(6, $H(Fixtures.functions).get('plus')(3)); - }, - - testIndex: function() { - this.assertUndefined($H().index('foo')); - - this.assert('a', $H(Fixtures.one).index('A#')); - this.assert('a', $H(Fixtures.many).index('A')); - this.assertUndefined($H(Fixtures.many).index('Z')) - - var hash = $H({a:1,b:'2',c:1}); - this.assert(['a','c'].include(hash.index(1))); - this.assertUndefined(hash.index('1')); - }, - - testMerge: function() { - var h = $H(Fixtures.many); - this.assertNotIdentical(h, h.merge()); - this.assertNotIdentical(h, h.merge({})); - this.assertInstanceOf(Hash, h.merge()); - this.assertInstanceOf(Hash, h.merge({})); - this.assertHashEqual(h, h.merge()); - this.assertHashEqual(h, h.merge({})); - this.assertHashEqual(h, h.merge($H())); - this.assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.merge({aaa: 'AAA'})); - this.assertHashEqual({a:'A#', b:'B', c:'C', d:'D#' }, h.merge(Fixtures.one)); - }, - - testUpdate: function() { - var h = $H(Fixtures.many); - this.assertIdentical(h, h.update()); - this.assertIdentical(h, h.update({})); - this.assertHashEqual(h, h.update()); - this.assertHashEqual(h, h.update({})); - this.assertHashEqual(h, h.update($H())); - this.assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update({aaa: 'AAA'})); - this.assertHashEqual({a:'A#', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update(Fixtures.one)); - }, - - testToQueryString: function() { - this.assertEqual('', $H({}).toQueryString()); - this.assertEqual('a%23=A', $H({'a#': 'A'}).toQueryString()); - this.assertEqual('a=A%23', $H(Fixtures.one).toQueryString()); - this.assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString()); - this.assertEqual("a=b&c", $H(Fixtures.value_undefined).toQueryString()); - this.assertEqual("a=b&c", $H("a=b&c".toQueryParams()).toQueryString()); - this.assertEqual("a=b+d&c", $H("a=b+d&c".toQueryParams()).toQueryString()); - this.assertEqual("a=b&c=", $H(Fixtures.value_null).toQueryString()); - this.assertEqual("a=b&c=0", $H(Fixtures.value_zero).toQueryString()); - this.assertEqual("color=r&color=g&color=b", $H(Fixtures.multiple).toQueryString()); - this.assertEqual("color=r&color=&color=g&color&color=0", $H(Fixtures.multiple_nil).toQueryString()); - this.assertEqual("color=&color", $H(Fixtures.multiple_all_nil).toQueryString()); - this.assertEqual("", $H(Fixtures.multiple_empty).toQueryString()); - this.assertEqual("", $H({foo: {}, bar: {}}).toQueryString()); - this.assertEqual("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.multiple_special).toQueryString()); - this.assertHashEqual(Fixtures.multiple_special, $H(Fixtures.multiple_special).toQueryString().toQueryParams()); - this.assertIdentical(Object.toQueryString, Hash.toQueryString); - - // Serializing newlines and spaces is weird. See: - // http://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#h-17.13.4.1 - var complex = "an arbitrary line\n\'something in single quotes followed by a newline\'\r\n" + - "and more text eventually"; - var queryString = $H({ val: complex }).toQueryString(); - var expected = "val=an+arbitrary+line%0D%0A'something+in+single+quotes+followed+by+a+" + - "newline'%0D%0Aand+more+text+eventually"; - this.assertEqual(expected, queryString, "newlines and spaces should be properly encoded"); - }, - - testInspect: function() { - this.assertEqual('#', $H({}).inspect()); - this.assertEqual("#", $H(Fixtures.one).inspect()); - this.assertEqual("#", $H(Fixtures.many).inspect()); - }, - - testClone: function() { - var h = $H(Fixtures.many); - this.assertHashEqual(h, h.clone()); - this.assertInstanceOf(Hash, h.clone()); - this.assertNotIdentical(h, h.clone()); - }, - - testToJSON: function() { - this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', - Object.toJSON({b: [undefined, false, true, undefined], c: {a: 'hello!'}})); - }, - - testAbilityToContainAnyKey: function() { - var h = $H({ _each: 'E', map: 'M', keys: 'K', pluck: 'P', unset: 'U' }); - this.assertEnumEqual($w('_each keys map pluck unset'), h.keys().sort()); - this.assertEqual('U', h.unset('unset')); - this.assertHashEqual({ _each: 'E', map: 'M', keys: 'K', pluck: 'P' }, h); - }, - - testHashToTemplateReplacements: function() { - var template = new Template("#{a} #{b}"), hash = $H({ a: "hello", b: "world" }); - this.assertEqual("hello world", template.evaluate(hash.toObject())); - this.assertEqual("hello world", template.evaluate(hash)); - this.assertEqual("hello", "#{a}".interpolate(hash)); - }, - - testPreventIterationOverShadowedProperties: function() { - // redundant now that object is systematically cloned. - var FooMaker = function(value) { - this.key = value; - }; - FooMaker.prototype.key = 'foo'; - var foo = new FooMaker('bar'); - this.assertEqual("key=bar", new Hash(foo).toQueryString()); - this.assertEqual("key=bar", new Hash(new Hash(foo)).toQueryString()); - }, - - testIterationWithEach: function() { - var h = $H({a:1, b:2}); - var result = [] - h.each(function(kv, i){ - result.push(i); - }); - this.assertEnumEqual([0,1], result); - } - -}); \ No newline at end of file diff --git a/test/unit.old/layout_test.js b/test/unit.old/layout_test.js deleted file mode 100644 index 03d61d165..000000000 --- a/test/unit.old/layout_test.js +++ /dev/null @@ -1,168 +0,0 @@ -function isDisplayed(element) { - var originalElement = element; - - while (element && element.parentNode) { - var display = element.getStyle('display'); - if (display === 'none') { - return false; - } - element = $(element.parentNode); - } - return true; -} - -new Test.Unit.Runner({ - 'test preCompute argument of layout': function() { - var preComputedLayout = $('box1').getLayout(true), - normalLayout = $('box1').getLayout(); - - // restore normal get method from Hash object - preComputedLayout.get = Hash.prototype.get; - - Element.Layout.PROPERTIES.each(function(key) { - this.assertEqual(normalLayout.get(key), preComputedLayout.get(key), key); - }, this); - }, - 'test layout on absolutely-positioned elements': function() { - var layout = $('box1').getLayout(); - - this.assertEqual(242, layout.get('width'), 'width' ); - this.assertEqual(555, layout.get('height'), 'height'); - - this.assertEqual(3, layout.get('border-left'), 'border-left'); - this.assertEqual(10, layout.get('padding-top'), 'padding-top'); - this.assertEqual(1020, layout.get('top'), 'top'); - - this.assertEqual(25, layout.get('left'), 'left'); - }, - - 'test layout on elements with display: none and exact width': function() { - var layout = $('box2').getLayout(); - - this.assert(!isDisplayed($('box2')), 'box should be hidden'); - - this.assertEqual(500, layout.get('width'), 'width'); - this.assertEqual( 3, layout.get('border-right'), 'border-right'); - this.assertEqual( 10, layout.get('padding-bottom'), 'padding-bottom'); - this.assertEqual(526, layout.get('border-box-width'), 'border-box-width'); - - this.assert(!isDisplayed($('box2')), 'box should still be hidden'); - }, - - 'test layout on elements with negative margins': function() { - var layout = $('box_with_negative_margins').getLayout(); - - this.assertEqual(-10, layout.get('margin-top') ); - this.assertEqual( -3, layout.get('margin-left') ); - this.assertEqual( 2, layout.get('margin-right')); - }, - - 'test layout on elements with display: none and width: auto': function() { - var layout = $('box3').getLayout(); - - this.assert(!isDisplayed($('box3')), 'box should be hidden'); - - this.assertEqual(364, layout.get('width'), 'width'); - this.assertEqual(400, layout.get('margin-box-width'), 'margin-box-width'); - this.assertEqual(390, layout.get('border-box-width'), 'border-box-width'); - this.assertEqual(3, layout.get('border-right'), 'border-top'); - this.assertEqual(10, layout.get('padding-bottom'), 'padding-right'); - - // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box3')), 'box should still be hidden'); - }, - - 'test layout on elements with display: none ancestors': function() { - var layout = $('box4').getLayout(); - - this.assert(!isDisplayed($('box4')), 'box should be hidden'); - - // Width and height values are nonsensical for deeply-hidden elements. - this.assertEqual(0, layout.get('width'), 'width of a deeply-hidden element should be 0'); - this.assertEqual(0, layout.get('margin-box-height'), 'height of a deeply-hidden element should be 0'); - - // But we can still get meaningful values for other measurements. - this.assertEqual(0, layout.get('border-right'), 'border-top'); - this.assertEqual(13, layout.get('padding-bottom'), 'padding-right'); - - // Ensure that we cleaned up after ourselves. - this.assert(!isDisplayed($('box4')), 'box should still be hidden'); - }, - - 'test positioning on absolutely-positioned elements': function() { - var layout = $('box5').getLayout(); - - this.assertEqual(30, layout.get('top'), 'top'); - this.assertEqual(60, layout.get('right'), 'right (percentage value)'); - - this.assertEqual(340, layout.get('left'), 'left'); - }, - - 'test positioning on absolutely-positioned element with top=0 and left=0': function() { - var layout = $('box6').getLayout(); - - this.assertEqual(0, layout.get('top'), 'top'); - this.assertIdentical($('box6_parent'), $('box6').getOffsetParent()); - }, - - 'test layout on statically-positioned element with percentage width': function() { - var layout = $('box7').getLayout(); - - this.assertEqual(150, layout.get('width')); - }, - - 'test layout on absolutely-positioned element with percentage width': function() { - var layout = $('box8').getLayout(); - - this.assertEqual(150, layout.get('width')); - }, - - 'test layout on fixed-position element with percentage width': function() { - var viewportWidth = document.viewport.getWidth(); - var layout = $('box9').getLayout(); - - this.info("NOTE: IE6 WILL fail these tests because it doesn't support position: fixed. This is expected."); - - function assertNear(v1, v2, message) { - var abs = Math.abs(v1 - v2); - this.assert(abs <= 1, message + ' (actual: ' + v1 + ', ' + v2 + ')'); - } - - // With percentage widths, we'll occasionally run into rounding - // discrepancies. Assert that the values agree to within 1 pixel. - var vWidth = viewportWidth / 4, eWidth = $('box9').measure('width'); - assertNear.call(this, vWidth, eWidth, 'width (visible)'); - - $('box9').hide(); - assertNear.call(this, vWidth, $('box9').measure('width'), 'width (hidden)'); - $('box9').show(); - }, - - 'test #toCSS, #toObject, #toHash': function() { - var layout = $('box6').getLayout(); - var top = layout.get('top'); - - var cssObject = layout.toCSS('top'); - - this.assert('top' in cssObject, - "layout object should have 'top' property"); - - cssObject = layout.toCSS('top left bottom'); - - $w('top left bottom').each( function(prop) { - this.assert(prop in cssObject, "layout object should have '" + - prop + "' property"); - }, this); - - var obj = layout.toObject('top'); - this.assert('top' in obj, - "object should have 'top' property"); - }, - - 'test dimensions on absolutely-positioned, hidden elements': function() { - var layout = $('box10').getLayout(); - - this.assertEqual(278, layout.get('width'), 'width' ); - this.assertEqual(591, layout.get('height'), 'height'); - } -}); diff --git a/test/unit.old/number_test.js b/test/unit.old/number_test.js deleted file mode 100644 index 2eb43a575..000000000 --- a/test/unit.old/number_test.js +++ /dev/null @@ -1,38 +0,0 @@ -new Test.Unit.Runner({ - - testNumberMathMethods: function() { - this.assertEqual(1, (0.9).round()); - this.assertEqual(-2, (-1.9).floor()); - this.assertEqual(-1, (-1.9).ceil()); - - $w('abs floor round ceil').each(function(method) { - this.assertEqual(Math[method](Math.PI), Math.PI[method]()); - }, this); - }, - - testNumberToColorPart: function() { - this.assertEqual('00', (0).toColorPart()); - this.assertEqual('0a', (10).toColorPart()); - this.assertEqual('ff', (255).toColorPart()); - }, - - testNumberToPaddedString: function() { - this.assertEqual('00', (0).toPaddedString(2, 16)); - this.assertEqual('0a', (10).toPaddedString(2, 16)); - this.assertEqual('ff', (255).toPaddedString(2, 16)); - this.assertEqual('000', (0).toPaddedString(3)); - this.assertEqual('010', (10).toPaddedString(3)); - this.assertEqual('100', (100).toPaddedString(3)); - this.assertEqual('1000', (1000).toPaddedString(3)); - }, - - testNumberTimes: function() { - var results = []; - (5).times(function(i) { results.push(i) }); - this.assertEnumEqual($R(0, 4), results); - - results = []; - (5).times(function(i) { results.push(i * this.i) }, { i: 2 }); - this.assertEnumEqual([0, 2, 4, 6, 8], results); - } -}); \ No newline at end of file diff --git a/test/unit.old/object_test.js b/test/unit.old/object_test.js deleted file mode 100644 index 782992e79..000000000 --- a/test/unit.old/object_test.js +++ /dev/null @@ -1,218 +0,0 @@ -new Test.Unit.Runner({ - testObjectExtend: function() { - var object = {foo: 'foo', bar: [1, 2, 3]}; - this.assertIdentical(object, Object.extend(object)); - this.assertHashEqual({foo: 'foo', bar: [1, 2, 3]}, object); - this.assertIdentical(object, Object.extend(object, {bla: 123})); - this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object); - this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: null}, - Object.extend(object, {bla: null})); - }, - - testObjectToQueryString: function() { - this.assertEqual('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'})); - }, - - testObjectClone: function() { - var object = {foo: 'foo', bar: [1, 2, 3]}; - this.assertNotIdentical(object, Object.clone(object)); - this.assertHashEqual(object, Object.clone(object)); - this.assertHashEqual({}, Object.clone()); - var clone = Object.clone(object); - delete clone.bar; - this.assertHashEqual({foo: 'foo'}, clone, - "Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted."); - }, - - testObjectKeys: function() { - this.assertEnumEqual([], Object.keys({})); - this.assertEnumEqual(['bar', 'foo'], Object.keys({foo: 'foo', bar: 'bar'}).sort()); - function Foo() { this.bar = 'bar'; } - Foo.prototype.foo = 'foo'; - this.assertEnumEqual(['bar'], Object.keys(new Foo())); - this.assertRaise('TypeError', function(){ Object.keys() }); - - var obj = { - foo: 'bar', - baz: 'thud', - toString: function() { return '1'; }, - valueOf: function() { return 1; } - }; - - this.assertEqual(4, Object.keys(obj).length, 'DontEnum properties should be included in Object.keys'); - }, - - testObjectInspect: function() { - this.assertEqual('undefined', Object.inspect()); - this.assertEqual('undefined', Object.inspect(undefined)); - this.assertEqual('null', Object.inspect(null)); - this.assertEqual("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar')); - this.assertEqual('[]', Object.inspect([])); - this.assertNothingRaised(function() { Object.inspect(window.Node) }); - }, - - testObjectToJSON: function() { - this.assertUndefined(Object.toJSON(undefined)); - this.assertUndefined(Object.toJSON(Prototype.K)); - this.assertEqual('\"\"', Object.toJSON('')); - this.assertEqual('\"test\"', Object.toJSON('test')); - this.assertEqual('null', Object.toJSON(Number.NaN)); - this.assertEqual('0', Object.toJSON(0)); - this.assertEqual('-293', Object.toJSON(-293)); - this.assertEqual('[]', Object.toJSON([])); - this.assertEqual('[\"a\"]', Object.toJSON(['a'])); - this.assertEqual('[\"a\",1]', Object.toJSON(['a', 1])); - this.assertEqual('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}])); - this.assertEqual('{\"a\":\"hello!\"}', Object.toJSON({a: 'hello!'})); - this.assertEqual('{}', Object.toJSON({})); - this.assertEqual('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K})); - this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', - Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})); - this.assertEqual('{\"b\":[null,false,true,null],\"c\":{\"a\":\"hello!\"}}', - Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}))); - this.assertEqual('true', Object.toJSON(true)); - this.assertEqual('false', Object.toJSON(false)); - this.assertEqual('null', Object.toJSON(null)); - var sam = new Person('sam'); - this.assertEqual('"-sam"', Object.toJSON(sam)); - }, - - testObjectToHTML: function() { - this.assertIdentical('', Object.toHTML()); - this.assertIdentical('', Object.toHTML('')); - this.assertIdentical('', Object.toHTML(null)); - this.assertIdentical('0', Object.toHTML(0)); - this.assertIdentical('123', Object.toHTML(123)); - this.assertEqual('hello world', Object.toHTML('hello world')); - this.assertEqual('hello world', Object.toHTML({toHTML: function() { return 'hello world' }})); - }, - - testObjectIsArray: function() { - this.assert(Object.isArray([])); - this.assert(Object.isArray([0])); - this.assert(Object.isArray([0, 1])); - this.assert(!Object.isArray({})); - this.assert(!Object.isArray($('list').childNodes)); - this.assert(!Object.isArray()); - this.assert(!Object.isArray('')); - this.assert(!Object.isArray('foo')); - this.assert(!Object.isArray(0)); - this.assert(!Object.isArray(1)); - this.assert(!Object.isArray(null)); - this.assert(!Object.isArray(true)); - this.assert(!Object.isArray(false)); - this.assert(!Object.isArray(undefined)); - }, - - testObjectIsHash: function() { - this.assert(Object.isHash($H())); - this.assert(Object.isHash(new Hash())); - this.assert(!Object.isHash({})); - this.assert(!Object.isHash(null)); - this.assert(!Object.isHash()); - this.assert(!Object.isHash('')); - this.assert(!Object.isHash(2)); - this.assert(!Object.isHash(false)); - this.assert(!Object.isHash(true)); - this.assert(!Object.isHash([])); - }, - - testObjectIsElement: function() { - this.assert(Object.isElement(document.createElement('div'))); - this.assert(Object.isElement(new Element('div'))); - this.assert(Object.isElement($('testlog'))); - this.assert(!Object.isElement(document.createTextNode('bla'))); - - // falsy variables should not mess up return value type - this.assertIdentical(false, Object.isElement(0)); - this.assertIdentical(false, Object.isElement('')); - this.assertIdentical(false, Object.isElement(NaN)); - this.assertIdentical(false, Object.isElement(null)); - this.assertIdentical(false, Object.isElement(undefined)); - }, - - testObjectIsFunction: function() { - this.assert(Object.isFunction(function() { })); - this.assert(Object.isFunction(Class.create())); - - this.assert(!Object.isFunction("a string")); - this.assert(!Object.isFunction($("testlog"))); - this.assert(!Object.isFunction([])); - this.assert(!Object.isFunction({})); - this.assert(!Object.isFunction(0)); - this.assert(!Object.isFunction(false)); - this.assert(!Object.isFunction(undefined)); - this.assert(!Object.isFunction(/xyz/), 'regular expressions are not functions'); - }, - - testObjectIsString: function() { - this.assert(!Object.isString(function() { })); - this.assert(Object.isString("a string")); - this.assert(Object.isString(new String("a string"))); - this.assert(!Object.isString(0)); - this.assert(!Object.isString([])); - this.assert(!Object.isString({})); - this.assert(!Object.isString(false)); - this.assert(!Object.isString(undefined)); - this.assert(!Object.isString(document), 'host objects should return false rather than throw exceptions'); - }, - - testObjectIsNumber: function() { - this.assert(Object.isNumber(0)); - this.assert(Object.isNumber(1.0)); - this.assert(Object.isNumber(new Number(0))); - this.assert(Object.isNumber(new Number(1.0))); - this.assert(!Object.isNumber(function() { })); - this.assert(!Object.isNumber({ test: function() { return 3 } })); - this.assert(!Object.isNumber("a string")); - this.assert(!Object.isNumber([])); - this.assert(!Object.isNumber({})); - this.assert(!Object.isNumber(false)); - this.assert(!Object.isNumber(undefined)); - this.assert(!Object.isNumber(document), 'host objects should return false rather than throw exceptions'); - }, - - testObjectIsDate: function() { - var d = new Date(); - this.assert(Object.isDate(d), 'constructor with no arguments'); - this.assert(Object.isDate(new Date(0)), 'constructor with milliseconds'); - this.assert(Object.isDate(new Date(1995, 11, 17)), 'constructor with Y, M, D'); - this.assert(Object.isDate(new Date(1995, 11, 17, 3, 24, 0)), 'constructor with Y, M, D, H, M, S'); - this.assert(Object.isDate(new Date(Date.parse("Dec 25, 1995"))), 'constructor with result of Date.parse'); - - this.assert(!Object.isDate(d.valueOf()), 'Date#valueOf returns a number'); - this.assert(!Object.isDate(function() { })); - this.assert(!Object.isDate(0)); - this.assert(!Object.isDate("a string")); - this.assert(!Object.isDate([])); - this.assert(!Object.isDate({})); - this.assert(!Object.isDate(false)); - this.assert(!Object.isDate(undefined)); - this.assert(!Object.isDate(document), 'host objects should return false rather than throw exceptions'); - }, - - testObjectIsUndefined: function() { - this.assert(Object.isUndefined(undefined)); - this.assert(!Object.isUndefined(null)); - this.assert(!Object.isUndefined(false)); - this.assert(!Object.isUndefined(0)); - this.assert(!Object.isUndefined("")); - this.assert(!Object.isUndefined(function() { })); - this.assert(!Object.isUndefined([])); - this.assert(!Object.isUndefined({})); - }, - - // sanity check - testDoesntExtendObjectPrototype: function() { - // for-in is supported with objects - var iterations = 0, obj = { a: 1, b: 2, c: 3 }; - for(property in obj) iterations++; - this.assertEqual(3, iterations); - - // for-in is not supported with arrays - iterations = 0; - var arr = [1,2,3]; - for(property in arr) iterations++; - this.assert(iterations > 3); - } -}); \ No newline at end of file diff --git a/test/unit.old/periodical_executer_test.js b/test/unit.old/periodical_executer_test.js deleted file mode 100644 index 4fa19099b..000000000 --- a/test/unit.old/periodical_executer_test.js +++ /dev/null @@ -1,35 +0,0 @@ -new Test.Unit.Runner({ - testPeriodicalExecuterStop: function() { - var peEventCount = 0; - function peEventFired(pe) { - if (++peEventCount > 2) pe.stop(); - } - - // peEventFired will stop the PeriodicalExecuter after 3 callbacks - new PeriodicalExecuter(peEventFired, 0.05); - - this.wait(600, function() { - this.assertEqual(3, peEventCount); - }); - }, - - testOnTimerEventMethod: function() { - var testcase = this, - pe = { - onTimerEvent: PeriodicalExecuter.prototype.onTimerEvent, - execute: function() { - testcase.assert(pe.currentlyExecuting); - } - }; - - pe.onTimerEvent(); - this.assert(!pe.currentlyExecuting); - - pe.execute = function() { - testcase.assert(pe.currentlyExecuting); - throw new Error() - } - this.assertRaise('Error', pe.onTimerEvent.bind(pe)); - this.assert(!pe.currentlyExecuting); - } -}); \ No newline at end of file diff --git a/test/unit.old/position_test.js b/test/unit.old/position_test.js deleted file mode 100644 index 5cea791b1..000000000 --- a/test/unit.old/position_test.js +++ /dev/null @@ -1,44 +0,0 @@ -var testVar = 'to be updated'; - -new Test.Unit.Runner({ - - setup: function() { - scrollTo(0,0); - Position.prepare(); - Position.includeScrollOffsets = false; - }, - - teardown: function() { - scrollTo(0,0); - Position.prepare(); - Position.includeScrollOffsets = false; - }, - - testPrepare: function() { - Position.prepare(); - this.assertEqual(0, Position.deltaX); - this.assertEqual(0, Position.deltaY); - scrollTo(20,30); - Position.prepare(); - this.assertEqual(20, Position.deltaX); - this.assertEqual(30, Position.deltaY); - }, - - testWithin: function() { - [true, false].each(function(withScrollOffsets) { - Position.includeScrollOffsets = withScrollOffsets; - this.assert(!Position.within($('body_absolute'), 9, 9), 'outside left/top'); - this.assert(Position.within($('body_absolute'), 10, 10), 'left/top corner'); - this.assert(Position.within($('body_absolute'), 10, 19), 'left/bottom corner'); - this.assert(!Position.within($('body_absolute'), 10, 20), 'outside bottom'); - }, this); - - scrollTo(20,30); - Position.prepare(); - Position.includeScrollOffsets = true; - this.assert(!Position.within($('body_absolute'), 9, 9), 'outside left/top'); - this.assert(Position.within($('body_absolute'), 10, 10), 'left/top corner'); - this.assert(Position.within($('body_absolute'), 10, 19), 'left/bottom corner'); - this.assert(!Position.within($('body_absolute'), 10, 20), 'outside bottom'); - } -}); \ No newline at end of file diff --git a/test/unit.old/prototype_test.js b/test/unit.old/prototype_test.js deleted file mode 100644 index be3cbb3ee..000000000 --- a/test/unit.old/prototype_test.js +++ /dev/null @@ -1,43 +0,0 @@ -new Test.Unit.Runner({ - testBrowserDetection: function() { - var results = $H(Prototype.Browser).map(function(engine){ - return engine; - }).partition(function(engine){ - return engine[1] === true - }); - var trues = results[0], falses = results[1]; - - this.info('User agent string is: ' + navigator.userAgent); - - this.assert(trues.size() == 0 || trues.size() == 1, - 'There should be only one or no browser detected.'); - - // we should have definite trues or falses here - trues.each(function(result) { - this.assert(result[1] === true); - }, this); - falses.each(function(result) { - this.assert(result[1] === false); - }, this); - - if(navigator.userAgent.indexOf('AppleWebKit/') > -1) { - this.info('Running on WebKit'); - this.assert(Prototype.Browser.WebKit); - } - - if(!!window.opera) { - this.info('Running on Opera'); - this.assert(Prototype.Browser.Opera); - } - - if(!!(window.attachEvent && !window.opera)) { - this.info('Running on IE'); - this.assert(Prototype.Browser.IE); - } - - if(navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1) { - this.info('Running on Gecko'); - this.assert(Prototype.Browser.Gecko); - } - } -}); \ No newline at end of file diff --git a/test/unit.old/range_test.js b/test/unit.old/range_test.js deleted file mode 100644 index c4618de1d..000000000 --- a/test/unit.old/range_test.js +++ /dev/null @@ -1,65 +0,0 @@ -new Test.Unit.Runner({ - - testInclude: function() { - this.assert(!$R(0, 0, true).include(0)); - this.assert($R(0, 0, false).include(0)); - - this.assert($R(0, 5, true).include(0)); - this.assert($R(0, 5, true).include(4)); - this.assert(!$R(0, 5, true).include(5)); - - this.assert($R(0, 5, false).include(0)); - this.assert($R(0, 5, false).include(5)); - this.assert(!$R(0, 5, false).include(6)); - }, - - testEach: function() { - var results = []; - $R(0, 0, true).each(function(value) { - results.push(value); - }); - - this.assertEnumEqual([], results); - - results = []; - $R(0, 3, false).each(function(value) { - results.push(value); - }); - - this.assertEnumEqual([0, 1, 2, 3], results); - - results = []; - $R(2, 4, true).each(function(value, index) { - results.push(index); - }); - this.assertEnumEqual([0, 1], results); - - }, - - testAny: function() { - this.assert(!$R(1, 1, true).any()); - this.assert($R(0, 3, false).any(function(value) { - return value == 3; - })); - }, - - testAll: function() { - this.assert($R(1, 1, true).all()); - this.assert($R(0, 3, false).all(function(value) { - return value <= 3; - })); - }, - - testToArray: function() { - this.assertEnumEqual([], $R(0, 0, true).toArray()); - this.assertEnumEqual([0], $R(0, 0, false).toArray()); - this.assertEnumEqual([0], $R(0, 1, true).toArray()); - this.assertEnumEqual([0, 1], $R(0, 1, false).toArray()); - this.assertEnumEqual([-3, -2, -1, 0, 1, 2], $R(-3, 3, true).toArray()); - this.assertEnumEqual([-3, -2, -1, 0, 1, 2, 3], $R(-3, 3, false).toArray()); - }, - - testDefaultsToNotExclusive: function() { - this.assertEnumEqual($R(-3,3), $R(-3,3,false)); - } -}); \ No newline at end of file diff --git a/test/unit.old/regexp_test.js b/test/unit.old/regexp_test.js deleted file mode 100644 index 9aadcd71d..000000000 --- a/test/unit.old/regexp_test.js +++ /dev/null @@ -1,42 +0,0 @@ -new Test.Unit.Runner({ - testRegExpEscape: function() { - this.assertEqual('word', RegExp.escape('word')); - this.assertEqual('\\/slashes\\/', RegExp.escape('/slashes/')); - this.assertEqual('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\')); - this.assertEqual('\\\\border of word', RegExp.escape('\\border of word')); - - this.assertEqual('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)')); - this.assertEqual('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]); - - this.assertEqual('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)')); - this.assertEqual('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]); - - this.assertEqual('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)')); - this.assertEqual('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]); - - this.assertEqual('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)')); - this.assertEqual('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]); - - this.assertEqual('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?', new RegExp(RegExp.escape('
    ')).exec('
    ')[0]); - - this.assertEqual('false', RegExp.escape(false)); - this.assertEqual('undefined', RegExp.escape()); - this.assertEqual('null', RegExp.escape(null)); - this.assertEqual('42', RegExp.escape(42)); - - this.assertEqual('\\\\n\\\\r\\\\t', RegExp.escape('\\n\\r\\t')); - this.assertEqual('\n\r\t', RegExp.escape('\n\r\t')); - this.assertEqual('\\{5,2\\}', RegExp.escape('{5,2}')); - - this.assertEqual( - '\\/\\(\\[\\.\\*\\+\\?\\^\\=\\!\\:\\$\\{\\}\\(\\)\\|\\[\\\\\\]\\\\\\\/\\\\\\\\\\]\\)\\/g', - RegExp.escape('/([.*+?^=!:${}()|[\\]\\/\\\\])/g') - ); - } -}); \ No newline at end of file diff --git a/test/unit.old/selector_engine_test.js b/test/unit.old/selector_engine_test.js deleted file mode 100644 index da428b01d..000000000 --- a/test/unit.old/selector_engine_test.js +++ /dev/null @@ -1,50 +0,0 @@ -/* -
    -
    -
    -
    -*/ - -new Test.Unit.Runner({ - testEngine: function() { - this.assert(Prototype.Selector.engine); - }, - - testSelect: function() { - var elements = Prototype.Selector.select('.test_class'); - - this.assert(Object.isArray(elements)); - this.assertEqual(2, elements.length); - this.assertEqual('test_div_parent', elements[0].id); - this.assertEqual('test_div_child', elements[1].id); - }, - - testSelectWithContext: function() { - var elements = Prototype.Selector.select('.test_class', $('test_div_parent')); - - this.assert(Object.isArray(elements)); - this.assertEqual(1, elements.length); - this.assertEqual('test_div_child', elements[0].id); - }, - - testSelectWithEmptyResult: function() { - var elements = Prototype.Selector.select('.non_existent'); - - this.assert(Object.isArray(elements)); - this.assertEqual(0, elements.length); - }, - - testMatch: function() { - var element = $('test_div_parent'); - - this.assertEqual(true, Prototype.Selector.match(element, '.test_class')); - this.assertEqual(false, Prototype.Selector.match(element, '.non_existent')); - }, - - testFind: function() { - var elements = document.getElementsByTagName('*'), - expression = '.test_class'; - this.assertEqual('test_div_parent', Prototype.Selector.find(elements, expression).id); - this.assertEqual('test_div_child', Prototype.Selector.find(elements, expression, 1).id); - } -}); \ No newline at end of file diff --git a/test/unit.old/selector_test.js b/test/unit.old/selector_test.js deleted file mode 100644 index ccb8a9147..000000000 --- a/test/unit.old/selector_test.js +++ /dev/null @@ -1,397 +0,0 @@ -var $RunBenchmarks = false; - -function reduce(arr) { - return arr.length > 1 ? arr : arr[0]; -} - -new Test.Unit.Runner({ - - testSelectorWithTagName: function() { - this.assertEnumEqual($A(document.getElementsByTagName('li')), $$('li')); - this.assertEnumEqual([$('strong')], $$('strong')); - this.assertEnumEqual([], $$('nonexistent')); - - var allNodes = $A(document.getElementsByTagName('*')).select( function(node) { - return node.tagName !== '!'; - }); - this.assertEnumEqual(allNodes, $$('*')); - }, - - testSelectorWithId: function() { - this.assertEnumEqual([$('fixtures')], $$('#fixtures')); - this.assertEnumEqual([], $$('#nonexistent')); - this.assertEnumEqual([$('troubleForm')], $$('#troubleForm')); - }, - - testSelectorWithClassName: function() { - this.assertEnumEqual($('p', 'link_1', 'item_1'), $$('.first')); - this.assertEnumEqual([], $$('.second')); - }, - - testSelectorWithTagNameAndId: function() { - this.assertEnumEqual([$('strong')], $$('strong#strong')); - this.assertEnumEqual([], $$('p#strong')); - }, - - testSelectorWithTagNameAndClassName: function() { - this.assertEnumEqual($('link_1', 'link_2'), $$('a.internal')); - this.assertEnumEqual([$('link_2')], $$('a.internal.highlight')); - this.assertEnumEqual([$('link_2')], $$('a.highlight.internal')); - this.assertEnumEqual([], $$('a.highlight.internal.nonexistent')); - }, - - testSelectorWithIdAndClassName: function() { - this.assertEnumEqual([$('link_2')], $$('#link_2.internal')); - this.assertEnumEqual([$('link_2')], $$('.internal#link_2')); - this.assertEnumEqual([$('link_2')], $$('#link_2.internal.highlight')); - this.assertEnumEqual([], $$('#link_2.internal.nonexistent')); - }, - - testSelectorWithTagNameAndIdAndClassName: function() { - this.assertEnumEqual([$('link_2')], $$('a#link_2.internal')); - this.assertEnumEqual([$('link_2')], $$('a.internal#link_2')); - this.assertEnumEqual([$('item_1')], $$('li#item_1.first')); - this.assertEnumEqual([], $$('li#item_1.nonexistent')); - this.assertEnumEqual([], $$('li#item_1.first.nonexistent')); - }, - - test$$MatchesAncestryWithTokensSeparatedByWhitespace: function() { - this.assertEnumEqual($('em2', 'em', 'span'), $$('#fixtures a *')); - this.assertEnumEqual([$('p')], $$('div#fixtures p')); - }, - - test$$CombinesResultsWhenMultipleExpressionsArePassed: function() { - this.assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li ')); - }, - - testSelectorWithTagNameAndAttributeExistence: function() { - this.assertEnumEqual($$('#fixtures h1'), $$('h1[class]'), 'h1[class]'); - this.assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]'), 'h1[CLASS]'); - this.assertEnumEqual([$('item_3')], $$('li#item_3[class]'), 'li#item_3[class]'); - }, - - testSelectorWithTagNameAndSpecificAttributeValue: function() { - this.assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]')); - this.assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]')); - }, - - testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() { - this.assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]'), "a[class~=\"internal\"]"); - this.assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]'), "a[class~=internal]"); - }, - - testSelectorWithAttributeAndNoTagName: function() { - this.assertEnumEqual($(document.body).select('a[href]'), $(document.body).select('[href]')); - this.assertEnumEqual($$('a[class~="internal"]'), $$('[class~=internal]')); - this.assertEnumEqual($$('*[id]'), $$('[id]')); - this.assertEnumEqual($('checked_radio', 'unchecked_radio'), $$('[type=radio]')); - this.assertEnumEqual($$('*[type=checkbox]'), $$('[type=checkbox]')); - this.assertEnumEqual($('with_title', 'commaParent'), $$('[title]')); - this.assertEnumEqual($$('#troubleForm *[type=radio]'), $$('#troubleForm [type=radio]')); - this.assertEnumEqual($$('#troubleForm *[type]'), $$('#troubleForm [type]')); - }, - - testSelectorWithAttributeContainingDash: function() { - this.assertEnumEqual([$('attr_with_dash')], $$('[foo-bar]'), "attribute with hyphen"); - }, - - testSelectorWithTagNameAndNegatedAttributeValue: function() { - this.assertEnumEqual([], $$('a[href!="#"]')); - }, - - testSelectorWithBracketAttributeValue: function() { - this.assertEnumEqual($('chk_1', 'chk_2'), $$('#troubleForm2 input[name="brackets[5][]"]')); - this.assertEnumEqual([$('chk_1')], $$('#troubleForm2 input[name="brackets[5][]"]:checked')); - this.assertEnumEqual([$('chk_2')], $$('#troubleForm2 input[name="brackets[5][]"][value=2]')); - }, - - test$$WithNestedAttributeSelectors: function() { - this.assertEnumEqual([$('strong')], $$('div[style] p[id] strong'), 'div[style] p[id] strong'); - }, - - testSelectorWithMultipleConditions: function() { - this.assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]'), - 'a[class~=external][href="#"]'); - this.assertEnumEqual([], $$('a[class~=external][href!="#"]'), - 'a[class~=external][href!="#"]'); - }, - - testSelectorMatchElements: function() { - this.assertElementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3'); - this.assertElementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2'); - this.assertEnumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last')); - this.assertElementsMatch(Selector.matchElements($('fixtures').descendants(), '.inexistant, a.internal'), '#link_1', '#link_2'); - }, - - testSelectorFindElement: function() { - this.assertElementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first'); - this.assertElementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2'); - this.assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li'); - this.assertEqual(undefined, Selector.findElement($('list').descendants(), 'em')); - }, - - testElementMatch: function() { - var span = $('dupL1'); - - // tests that should pass - this.assert(span.match('span')); - this.assert(span.match('span#dupL1')); - this.assert(span.match('div > span'), 'child combinator'); - this.assert(span.match('#dupContainer span'), 'descendant combinator'); - this.assert(span.match('#dupL1'), 'ID only'); - this.assert(span.match('span.span_foo'), 'class name 1'); - this.assert(span.match('span.span_bar'), 'class name 2'); - this.assert(span.match('span:first-child'), 'first-child pseudoclass'); - - this.assert(!span.match('span.span_wtf'), 'bogus class name'); - this.assert(!span.match('#dupL2'), 'different ID'); - this.assert(!span.match('div'), 'different tag name'); - this.assert(!span.match('span span'), 'different ancestry'); - this.assert(!span.match('span > span'), 'different parent'); - this.assert(!span.match('span:nth-child(5)'), 'different pseudoclass'); - - this.assert(!$('link_2').match('a[rel^=external]')); - this.assert($('link_1').match('a[rel^=external]')); - this.assert($('link_1').match('a[rel^="external"]')); - this.assert($('link_1').match("a[rel^='external']")); - - this.assert(span.match({ match: function(element) { return true }}), 'custom selector'); - this.assert(!span.match({ match: function(element) { return false }}), 'custom selector'); - }, - - testSelectorWithSpaceInAttributeValue: function() { - this.assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]')); - }, - - // AND NOW COME THOSE NEW TESTS AFTER ANDREW'S REWRITE! - - testSelectorWithChild: function() { - this.assertEnumEqual($('link_1', 'link_2'), $$('p.first > a')); - this.assertEnumEqual($('father', 'uncle'), $$('div#grandfather > div')); - this.assertEnumEqual($('level2_1', 'level2_2'), $$('#level1>span')); - this.assertEnumEqual($('level2_1', 'level2_2'), $$('#level1 > span')); - this.assertEnumEqual($('level3_1', 'level3_2'), $$('#level2_1 > *')); - this.assertEnumEqual([], $$('div > #nonexistent')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#level1 > span') }, 1000); - }); - }, - - testSelectorWithAdjacence: function() { - this.assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers')); - this.assertEnumEqual([$('uncle')], $$('div.brothers + div')); - this.assertEqual($('level2_2'), reduce($$('#level2_1+span'))); - this.assertEqual($('level2_2'), reduce($$('#level2_1 + span'))); - this.assertEqual($('level2_2'), reduce($$('#level2_1 + *'))); - this.assertEnumEqual([], $$('#level2_2 + span')); - this.assertEqual($('level3_2'), reduce($$('#level3_1 + span'))); - this.assertEqual($('level3_2'), reduce($$('#level3_1 + *'))); - this.assertEnumEqual([], $$('#level3_2 + *')); - this.assertEnumEqual([], $$('#level3_1 + em')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#level3_1 + span') }, 1000); - }); - }, - - testSelectorWithLaterSibling: function() { - this.assertEnumEqual([$('list')], $$('h1 ~ ul')); - this.assertEqual($('level2_2'), reduce($$('#level2_1 ~ span'))); - this.assertEnumEqual($('level2_2', 'level2_3'), reduce($$('#level2_1 ~ *'))); - this.assertEnumEqual([], $$('#level2_2 ~ span')); - this.assertEnumEqual([], $$('#level3_2 ~ *')); - this.assertEnumEqual([], $$('#level3_1 ~ em')); - this.assertEnumEqual([$('level3_2')], $$('#level3_1 ~ #level3_2')); - this.assertEnumEqual([$('level3_2')], $$('span ~ #level3_2')); - this.assertEnumEqual([], $$('div ~ #level3_2')); - this.assertEnumEqual([], $$('div ~ #level2_3')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#level2_1 ~ span') }, 1000); - }); - }, - - testSelectorWithNewAttributeOperators: function() { - this.assertEnumEqual($('father', 'uncle'), $$('div[class^=bro]'), 'matching beginning of string'); - this.assertEnumEqual($('father', 'uncle'), $$('div[class$=men]'), 'matching end of string'); - this.assertEnumEqual($('father', 'uncle'), $$('div[class*="ers m"]'), 'matching substring') - this.assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^="level2_"]')); - this.assertEnumEqual($('level2_1', 'level2_2', 'level2_3'), $$('#level1 *[id^=level2_]')); - this.assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$="_1"]')); - this.assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 *[id$=_1]')); - this.assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*="2"]')); - this.assertEnumEqual($('level2_1', 'level3_2', 'level2_2', 'level2_3'), $$('#level1 *[id*=2]')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#level1 *[id^=level2_]') }, 1000, '[^=]'); - this.benchmark(function() { $$('#level1 *[id$=_1]') }, 1000, '[$=]'); - this.benchmark(function() { $$('#level1 *[id*=_2]') }, 1000, '[*=]'); - }); - }, - - testSelectorWithDuplicates: function() { - this.assertEnumEqual($$('div div'), $$('div div').uniq()); - this.assertEnumEqual($('dupL2', 'dupL3', 'dupL4', 'dupL5'), $$('#dupContainer span span')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#dupContainer span span') }, 1000); - }); - }, - - testSelectorWithFirstLastOnlyNthNthLastChild: function() { - this.assertEnumEqual([$('level2_1')], $$('#level1>*:first-child')); - this.assertEnumEqual($('level2_1', 'level3_1', 'level_only_child'), $$('#level1 *:first-child')); - this.assertEnumEqual([$('level2_3')], $$('#level1>*:last-child')); - this.assertEnumEqual($('level3_2', 'level_only_child', 'level2_3'), $$('#level1 *:last-child')); - this.assertEnumEqual([$('level2_3')], $$('#level1>div:last-child')); - this.assertEnumEqual([$('level2_3')], $$('#level1 div:last-child')); - this.assertEnumEqual([], $$('#level1>div:first-child')); - this.assertEnumEqual([], $$('#level1>span:last-child')); - this.assertEnumEqual($('level2_1', 'level3_1'), $$('#level1 span:first-child')); - this.assertEnumEqual([], $$('#level1:first-child')); - this.assertEnumEqual([], $$('#level1>*:only-child')); - this.assertEnumEqual([$('level_only_child')], $$('#level1 *:only-child')); - this.assertEnumEqual([], $$('#level1:only-child')); - this.assertEnumEqual([$('link_2')], $$('#p *:nth-last-child(2)'), 'nth-last-child'); - this.assertEnumEqual([$('link_2')], $$('#p *:nth-child(3)'), 'nth-child'); - this.assertEnumEqual([$('link_2')], $$('#p a:nth-child(3)'), 'nth-child'); - this.assertEnumEqual($('item_2', 'item_3'), $$('#list > li:nth-child(n+2)')); - this.assertEnumEqual($('item_1', 'item_2'), $$('#list > li:nth-child(-n+2)')); - $RunBenchmarks && this.wait(500, function() { - this.benchmark(function() { $$('#level1 *:first-child') }, 1000, ':first-child'); - this.benchmark(function() { $$('#level1 *:last-child') }, 1000, ':last-child'); - this.benchmark(function() { $$('#level1 *:only-child') }, 1000, ':only-child'); - }); - }, - - testSelectorWithFirstLastNthNthLastOfType: function() { - this.assertEnumEqual([$('link_2')], $$('#p a:nth-of-type(2)'), 'nth-of-type'); - this.assertEnumEqual([$('link_1')], $$('#p a:nth-of-type(1)'), 'nth-of-type'); - this.assertEnumEqual([$('link_2')], $$('#p a:nth-last-of-type(1)'), 'nth-last-of-type'); - this.assertEnumEqual([$('link_1')], $$('#p a:first-of-type'), 'first-of-type'); - this.assertEnumEqual([$('link_2')], $$('#p a:last-of-type'), 'last-of-type'); - }, - - testSelectorWithNot: function() { - this.assertEnumEqual([$('link_2')], $$('#p a:not(a:first-of-type)'), 'first-of-type'); - this.assertEnumEqual([$('link_1')], $$('#p a:not(a:last-of-type)'), 'last-of-type'); - this.assertEnumEqual([$('link_2')], $$('#p a:not(a:nth-of-type(1))'), 'nth-of-type'); - this.assertEnumEqual([$('link_1')], $$('#p a:not(a:nth-last-of-type(1))'), 'nth-last-of-type'); - this.assertEnumEqual([$('link_2')], $$('#p a:not([rel~=nofollow])'), 'attribute 1'); - this.assertEnumEqual([$('link_2')], $$('#p a:not(a[rel^=external])'), 'attribute 2'); - this.assertEnumEqual([$('link_2')], $$('#p a:not(a[rel$=nofollow])'), 'attribute 3'); - this.assertEnumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"]) > em'), 'attribute 4') - this.assertEnumEqual([$('item_2')], $$('#list li:not(#item_1):not(#item_3)'), 'adjacent :not clauses'); - this.assertEnumEqual([$('son')], $$('#grandfather > div:not(#uncle) #son')); - this.assertEnumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"]) em'), 'attribute 4 + all descendants'); - this.assertEnumEqual([$('em')], $$('#p a:not(a[rel$="nofollow"])>em'), 'attribute 4 (without whitespace)'); - }, - - testSelectorWithEnabledDisabledChecked: function() { - this.assertEnumEqual([$('disabled_text_field')], $$('#troubleForm > *:disabled'), ':disabled'); - this.assertEnumEqual($('troubleForm').getInputs().without($('disabled_text_field')), $$('#troubleForm > *:enabled'), ':enabled'); - this.assertEnumEqual($('checked_box', 'checked_radio'), $$('#troubleForm *:checked'), ':checked'); - }, - - testSelectorWithEmpty: function() { - $('level3_1').innerHTML = ""; - this.assertEnumEqual($('level3_1', 'level3_2', 'level2_3'), - $$('#level1 *:empty'), '#level1 *:empty'); - this.assertEnumEqual([], $$('#level_only_child:empty'), 'newlines count as content!'); - }, - - testIdenticalResultsFromEquivalentSelectors: function() { - this.assertEnumEqual($$('div.brothers'), $$('div[class~=brothers]')); - this.assertEnumEqual($$('div.brothers'), $$('div[class~=brothers].brothers')); - this.assertEnumEqual($$('div:not(.brothers)'), $$('div:not([class~=brothers])')); - this.assertEnumEqual($$('li ~ li'), $$('li:not(:first-child)')); - this.assertEnumEqual($$('ul > li'), $$('ul > li:nth-child(n)')); - this.assertEnumEqual($$('ul > li:nth-child(even)'), $$('ul > li:nth-child(2n)')); - this.assertEnumEqual($$('ul > li:nth-child(odd)'), $$('ul > li:nth-child(2n+1)')); - this.assertEnumEqual($$('ul > li:first-child'), $$('ul > li:nth-child(1)')); - this.assertEnumEqual($$('ul > li:last-child'), $$('ul > li:nth-last-child(1)')); - this.assertEnumEqual($$('ul > li:nth-child(n-999)'), $$('ul > li')); - this.assertEnumEqual($$('ul>li'), $$('ul > li')); - this.assertEnumEqual($$('#p a:not(a[rel$="nofollow"])>em'), $$('#p a:not(a[rel$="nofollow"]) > em')) - }, - - testSelectorsThatShouldReturnNothing: function() { - this.assertEnumEqual([], $$('span:empty > *')); - this.assertEnumEqual([], $$('div.brothers:not(.brothers)')); - this.assertEnumEqual([], $$('#level2_2 :only-child:not(:last-child)')); - this.assertEnumEqual([], $$('#level2_2 :only-child:not(:first-child)')); - }, - - testCommasFor$$: function() { - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first, #item_3, #troubleForm')); - this.assertEnumEqual($('p', 'link_1', 'list', 'item_1', 'item_3', 'troubleForm'), $$('#list, .first', '#item_3, #troubleForm')); - this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"], input[value="#commaOne,#commaTwo"]')); - this.assertEnumEqual($('commaParent', 'commaChild'), $$('form[title*="commas,"]', 'input[value="#commaOne,#commaTwo"]')); - }, - - testSelectorExtendsAllNodes: function(){ - var element = document.createElement('div'); - (3).times(function(){ - element.appendChild(document.createElement('div')); - }); - element.setAttribute('id','scratch_element'); - $$('body')[0].appendChild(element); - - var results = $$('#scratch_element div'); - this.assert(typeof results[0].show == 'function'); - this.assert(typeof results[1].show == 'function'); - this.assert(typeof results[2].show == 'function'); - }, - - testCopiedNodesGetIncluded: function() { - this.assertElementsMatch( - Selector.matchElements($('counted_container').descendants(), 'div'), - 'div.is_counted' - ); - $('counted_container').innerHTML += $('counted_container').innerHTML; - this.assertElementsMatch( - Selector.matchElements($('counted_container').descendants(), 'div'), 'div.is_counted', - 'div.is_counted' - ); - }, - - testSelectorNotInsertedNodes: function() { - window.debug = true; - var wrapper = new Element("div"); - wrapper.update("
    "); - this.assertNotNullOrUndefined(wrapper.select('[id=myTD]')[0], - 'selecting: [id=myTD]'); - this.assertNotNullOrUndefined(wrapper.select('#myTD')[0], - 'selecting: #myTD'); - this.assertNotNullOrUndefined(wrapper.select('td')[0], - 'selecting: td'); - this.assert($$('#myTD').length == 0, - 'should not turn up in document-rooted search'); - window.debug = false; - }, - - testElementDown: function() { - var a = $('dupL4'); - var b = $('dupContainer').down('#dupL4'); - - this.assertEqual(a, b); - }, - testElementDownWithDotAndColon: function() { - var a = $('dupL4_dotcolon'); - var b = $('dupContainer.withdot:active').down('#dupL4_dotcolon'); - var c = $('dupContainer.withdot:active').select('#dupL4_dotcolon'); - - this.assertEqual(a, b); - this.assertEnumEqual([a], c); - }, - - testDescendantSelectorBuggy: function() { - var el = document.createElement('div'); - el.innerHTML = '
    '; - document.body.appendChild(el); - this.assertEqual(2, $(el).select('ul li').length); - document.body.removeChild(el); - }, - - testFindElementWithIndexWhenElementsAreNotInDocumentOrder: function() { - var ancestors = $("target_1").ancestors(); - this.assertEqual($("container_2"), Selector.findElement(ancestors, "[container], .container", 0)); - this.assertEqual($("container_1"), Selector.findElement(ancestors, "[container], .container", 1)); - } -}); \ No newline at end of file diff --git a/test/unit.old/string_test.js b/test/unit.old/string_test.js deleted file mode 100644 index b000ad6cd..000000000 --- a/test/unit.old/string_test.js +++ /dev/null @@ -1,579 +0,0 @@ -new Test.Unit.Runner({ - testInterpret: function(){ - this.assertIdentical('true', String.interpret(true)); - this.assertIdentical('123', String.interpret(123)); - this.assertIdentical('foo bar', String.interpret('foo bar')); - this.assertIdentical( - 'object string', - String.interpret({ toString: function(){ return 'object string' } })); - - this.assertIdentical('0', String.interpret(0)); - this.assertIdentical('false', String.interpret(false)); - this.assertIdentical('', String.interpret(undefined)); - this.assertIdentical('', String.interpret(null)); - this.assertIdentical('', String.interpret('')); - }, - - testGsubWithReplacementFunction: function() { - var source = 'foo boo boz'; - - this.assertEqual('Foo Boo BoZ', - source.gsub(/[^o]+/, function(match) { - return match[0].toUpperCase() - })); - this.assertEqual('f2 b2 b1z', - source.gsub(/o+/, function(match) { - return match[0].length; - })); - this.assertEqual('f0 b0 b1z', - source.gsub(/o+/, function(match) { - return match[0].length % 2; - })); - - }, - - testGsubWithReplacementString: function() { - var source = 'foo boo boz'; - - this.assertEqual('foobooboz', - source.gsub(/\s+/, '')); - this.assertEqual(' z', - source.gsub(/(.)(o+)/, '')); - - this.assertEqual('ウィメンズ2007
    クルーズコレクション', - 'ウィメンズ2007\nクルーズコレクション'.gsub(/\n/,'
    ')); - this.assertEqual('ウィメンズ2007
    クルーズコレクション', - 'ウィメンズ2007\nクルーズコレクション'.gsub('\n','
    ')); - - this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar', - source.gsub('', 'bar'), 'empty string'); - this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar', - source.gsub(new RegExp(''), 'bar'), 'empty regexp'); - }, - - testGsubWithReplacementTemplateString: function() { - var source = 'foo boo boz'; - - this.assertEqual('-oo-#{1}- -oo-#{1}- -o-#{1}-z', - source.gsub(/(.)(o+)/, '-#{2}-\\#{1}-')); - this.assertEqual('-foo-f- -boo-b- -bo-b-z', - source.gsub(/(.)(o+)/, '-#{0}-#{1}-')); - this.assertEqual('-oo-f- -oo-b- -o-b-z', - source.gsub(/(.)(o+)/, '-#{2}-#{1}-')); - this.assertEqual(' z', - source.gsub(/(.)(o+)/, '#{3}')); - }, - - testGsubWithTroublesomeCharacters: function() { - this.assertEqual('ab', 'a|b'.gsub('|', '')); - this.assertEqual('ab', 'ab(?:)'.gsub('(?:)', '')); - this.assertEqual('ab', 'ab()'.gsub('()', '')); - this.assertEqual('ab', 'ab'.gsub('^', '')); - this.assertEqual('ab', 'a?b'.gsub('?', '')) - this.assertEqual('ab', 'a+b'.gsub('+', '')); - this.assertEqual('ab', 'a*b'.gsub('*', '')); - this.assertEqual('ab', 'a{1}b'.gsub('{1}', '')); - this.assertEqual('ab', 'a.b'.gsub('.', '')); - }, - - testGsubWithZeroLengthMatch: function() { - this.assertEqual('ab', 'ab'.gsub('', '')); - this.assertEqual('a', 'a'.gsub(/b*/, 'c')); - this.assertEqual('abc', 'abc'.gsub(/b{0}/, '')); - }, - - testSubWithReplacementFunction: function() { - var source = 'foo boo boz'; - - this.assertEqual('Foo boo boz', - source.sub(/[^o]+/, function(match) { - return match[0].toUpperCase() - }), 1); - this.assertEqual('Foo Boo boz', - source.sub(/[^o]+/, function(match) { - return match[0].toUpperCase() - }, 2), 2); - this.assertEqual(source, - source.sub(/[^o]+/, function(match) { - return match[0].toUpperCase() - }, 0), 0); - this.assertEqual(source, - source.sub(/[^o]+/, function(match) { - return match[0].toUpperCase() - }, -1), -1); - }, - - testSubWithReplacementString: function() { - var source = 'foo boo boz'; - - this.assertEqual('oo boo boz', - source.sub(/[^o]+/, '')); - this.assertEqual('oooo boz', - source.sub(/[^o]+/, '', 2)); - this.assertEqual('-f-oo boo boz', - source.sub(/[^o]+/, '-#{0}-')); - this.assertEqual('-f-oo- b-oo boz', - source.sub(/[^o]+/, '-#{0}-', 2)); - }, - - testScan: function() { - var source = 'foo boo boz', results = []; - var str = source.scan(/[o]+/, function(match) { - results.push(match[0].length); - }); - this.assertEnumEqual([2, 2, 1], results); - this.assertEqual(source, source.scan(/x/, this.fail)); - this.assert(typeof str == 'string'); - }, - - testToArray: function() { - this.assertEnumEqual([],''.toArray()); - this.assertEnumEqual(['a'],'a'.toArray()); - this.assertEnumEqual(['a','b'],'ab'.toArray()); - this.assertEnumEqual(['f','o','o'],'foo'.toArray()); - }, - - /* - Note that camelize() differs from its Rails counterpart, - as it is optimized for dealing with JavaScript object - properties in conjunction with CSS property names: - - Looks for dashes, not underscores - - CamelCases first word if there is a front dash - */ - testCamelize: function() { - this.assertEqual('', ''.camelize()); - this.assertEqual('', '-'.camelize()); - this.assertEqual('foo', 'foo'.camelize()); - this.assertEqual('foo_bar', 'foo_bar'.camelize()); - this.assertEqual('FooBar', '-foo-bar'.camelize()); - this.assertEqual('FooBar', 'FooBar'.camelize()); - - this.assertEqual('fooBar', 'foo-bar'.camelize()); - this.assertEqual('borderBottomWidth', 'border-bottom-width'.camelize()); - - this.assertEqual('classNameTest','class-name-test'.camelize()); - this.assertEqual('classNameTest','className-test'.camelize()); - this.assertEqual('classNameTest','class-nameTest'.camelize()); - - /* this.benchmark(function(){ - 'class-name-test'.camelize(); - },10000); */ - }, - - testCapitalize: function() { - this.assertEqual('',''.capitalize()); - this.assertEqual('Ä','ä'.capitalize()); - this.assertEqual('A','A'.capitalize()); - this.assertEqual('Hello','hello'.capitalize()); - this.assertEqual('Hello','HELLO'.capitalize()); - this.assertEqual('Hello','Hello'.capitalize()); - this.assertEqual('Hello world','hello WORLD'.capitalize()); - }, - - testUnderscore: function() { - this.assertEqual('', ''.underscore()); - this.assertEqual('_', '-'.underscore()); - this.assertEqual('foo', 'foo'.underscore()); - this.assertEqual('foo', 'Foo'.underscore()); - this.assertEqual('foo_bar', 'foo_bar'.underscore()); - this.assertEqual('border_bottom', 'borderBottom'.underscore()); - this.assertEqual('border_bottom_width', 'borderBottomWidth'.underscore()); - this.assertEqual('border_bottom_width', 'border-Bottom-Width'.underscore()); - }, - - testDasherize: function() { - this.assertEqual('', ''.dasherize()); - this.assertEqual('foo', 'foo'.dasherize()); - this.assertEqual('Foo', 'Foo'.dasherize()); - this.assertEqual('foo-bar', 'foo-bar'.dasherize()); - this.assertEqual('border-bottom-width', 'border_bottom_width'.dasherize()); - }, - - testTruncate: function() { - var source = 'foo boo boz foo boo boz foo boo boz foo boo boz'; - this.assertEqual(source, source.truncate(source.length)); - this.assertEqual('foo boo boz foo boo boz foo...', source.truncate(0)); - this.assertEqual('fo...', source.truncate(5)); - this.assertEqual('foo b', source.truncate(5, '')); - - this.assert(typeof 'foo'.truncate(5) == 'string'); - this.assert(typeof 'foo bar baz'.truncate(5) == 'string'); - }, - - testStrip: function() { - this.assertEqual('hello world', ' hello world '.strip()); - this.assertEqual('hello world', 'hello world'.strip()); - this.assertEqual('hello \n world', ' hello \n world '.strip()); - this.assertEqual('', ' '.strip()); - }, - - testStripTags: function() { - this.assertEqual('hello world', 'hello world'.stripTags()); - this.assertEqual('hello world', 'hello world'.stripTags()); - this.assertEqual('hello world', 'hello world'.stripTags()); - this.assertEqual('hello world', 'hello world'.stripTags()); - this.assertEqual('1\n2', '1\n2'.stripTags()); - this.assertEqual('one < two blah baz', 'one < two blah baz'.stripTags()); - }, - - testStripScripts: function() { - this.assertEqual('foo bar', 'foo bar'.stripScripts()); - this.assertEqual('foo bar', ('foo - <%= script_tag('assets/prototype.js') %> - <%= script_tag('lib_assets/unittest.js') %> - <%= link_tag('lib_assets/unittest.css') %> - <%= css_fixtures %> - <%= js_fixtures %> - <%= test_file %> - - - -
    - -<%= html_fixtures %> - - - diff --git a/test/unit.old/unittest_test.js b/test/unit.old/unittest_test.js deleted file mode 100644 index 0f79bd4f0..000000000 --- a/test/unit.old/unittest_test.js +++ /dev/null @@ -1,148 +0,0 @@ -var testObj = { - isNice: function() { - return true; - }, - isBroken: function() { - return false; - } -} - -new Test.Unit.Runner({ - - testIsRunningFromRake: function() { - if (window.location.toString().startsWith('http')) { - this.assert(this.isRunningFromRake); - this.info('These tests are running from rake.') - } else { - this.assert(!this.isRunningFromRake); - this.info('These tests are *not* running from rake.') - } - }, - - testBuildMessage: function() { - this.assertEqual("'foo' 'bar'", this.buildMessage('', '? ?', 'foo', 'bar')) - }, - - testAssertEqual: function() { - this.assertEqual(0, 0); - this.assertEqual(0, 0, "test"); - - this.assertEqual(0,'0'); - this.assertEqual(65.0, 65); - - this.assertEqual("a", "a"); - this.assertEqual("a", "a", "test"); - - this.assertNotEqual(0, 1); - this.assertNotEqual("a","b"); - this.assertNotEqual({},{}); - this.assertNotEqual([],[]); - this.assertNotEqual([],{}); - }, - - testAssertEnumEqual: function() { - this.assertEnumEqual([], []); - this.assertEnumEqual(['a', 'b'], ['a', 'b']); - this.assertEnumEqual(['1', '2'], [1, 2]); - this.assertEnumNotEqual(['1', '2'], [1, 2, 3]); - }, - - testAssertHashEqual: function() { - this.assertHashEqual({}, {}); - this.assertHashEqual({a:'b'}, {a:'b'}); - this.assertHashEqual({a:'b', c:'d'}, {c:'d', a:'b'}); - this.assertHashNotEqual({a:'b', c:'d'}, {c:'d', a:'boo!'}); - }, - - testAssertRespondsTo: function() { - this.assertRespondsTo('isNice', testObj); - this.assertRespondsTo('isBroken', testObj); - }, - - testAssertIdentical: function() { - this.assertIdentical(0, 0); - this.assertIdentical(0, 0, "test"); - this.assertIdentical(1, 1); - this.assertIdentical('a', 'a'); - this.assertIdentical('a', 'a', "test"); - this.assertIdentical('', ''); - this.assertIdentical(undefined, undefined); - this.assertIdentical(null, null); - this.assertIdentical(true, true); - this.assertIdentical(false, false); - - var obj = {a:'b'}; - this.assertIdentical(obj, obj); - - this.assertNotIdentical({1:2,3:4},{1:2,3:4}); - - this.assertIdentical(1, 1.0); // both are typeof == 'number' - - this.assertNotIdentical(1, '1'); - this.assertNotIdentical(1, '1.0'); - }, - - testAssertNullAndAssertUndefined: function() { - this.assertNull(null); - this.assertNotNull(undefined); - this.assertNotNull(0); - this.assertNotNull(''); - this.assertNotUndefined(null); - this.assertUndefined(undefined); - this.assertNotUndefined(0); - this.assertNotUndefined(''); - this.assertNullOrUndefined(null); - this.assertNullOrUndefined(undefined); - this.assertNotNullOrUndefined(0); - this.assertNotNullOrUndefined(''); - }, - - testAssertMatch: function() { - this.assertMatch(/knowmad.jpg$/, 'http://script.aculo.us/images/knowmad.jpg'); - this.assertMatch(/Fuc/, 'Thomas Fuchs'); - this.assertMatch(/^\$(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/, '$19.95'); - this.assertMatch(/(\d{3}\) ?)|(\d{3}[- \.])?\d{3}[- \.]\d{4}(\s(x\d+)?){0,1}$/, '704-343-9330'); - this.assertMatch(/^(?:(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(\/|-|\.)(?:0?2\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(\/|-|\.)(?:(?:(?:0?[13578]|1[02])\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\2(?:0?[1-9]|1\d|2[0-8]))))$/, '2001-06-16'); - this.assertMatch(/^((0?[123456789])|(1[012]))\s*:\s*([012345]\d)(\s*:\s*([012345]\d))?\s*[ap]m\s*-\s*((0?[123456789])|(1[012]))\s*:\s*([012345]\d)(\s*:\s*([012345]\d))?\s*[ap]m$/i, '2:00PM-2:15PM'); - this.assertNoMatch(/zubar/, 'foo bar'); - }, - - testAssertInstanceOf: function() { - this.assertInstanceOf(String, new String); - this.assertInstanceOf(RegExp, /foo/); - this.assertNotInstanceOf(String, {}); - }, - - testAssertVisible: function() { - this.assertVisible('testcss1'); - this.assertNotVisible('testcss1_span'); - //this.assertNotVisible('testcss2', "Due to a Safari bug, this test fails in Safari."); - - Element.hide('testcss1'); - this.assertNotVisible('testcss1'); - this.assertNotVisible('testcss1_span'); - Element.show('testcss1'); - this.assertVisible('testcss1'); - this.assertNotVisible('testcss1_span'); - - Element.show('testcss1_span'); - this.assertVisible('testcss1_span'); - Element.hide('testcss1'); - this.assertNotVisible('testcss1_span'); // hidden by parent - }, - - testAssertElementsMatch: function() { - this.assertElementsMatch($$('#tlist'), '#tlist'); - this.assertElementMatches($('tlist'), '#tlist'); - } -}); - -new Test.Unit.Runner({ - testDummy: function() { - this.assert(true); - }, - - testMultipleTestRunner: function() { - this.assertEqual('passed', $('testlog_2').down('td', 1).innerHTML); - } -}, {testLog: 'testlog_2'}); \ No newline at end of file From f2027545e7babad5b71132b0c7458edbf8cdf387 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 5 Nov 2014 21:29:22 -0600 Subject: [PATCH 463/502] Make the new testing tasks the defaults. NOTE: We are getting rid of the caja:test task because I do not believe it is used anymore. If I am wrong, please let me know. --- Rakefile | 116 ++------------------------------------------ test/unit/server.rb | 2 +- 2 files changed, 6 insertions(+), 112 deletions(-) diff --git a/Rakefile b/Rakefile index a34151813..e0708b6b8 100755 --- a/Rakefile +++ b/Rakefile @@ -276,97 +276,15 @@ task :clean_package_source do rm_rf File.join(PrototypeHelper::PKG_DIR, "prototype-#{PrototypeHelper::VERSION}") end -task :test => ['test:build', 'test:run'] +task :test => ['test:require', 'test:start'] namespace :test do - desc 'Runs all the JavaScript unit tests and collects the results' - task :run => [:require] do - testcases = ENV['TESTCASES'] - browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',') - tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',') - runner = UnittestJS::WEBrickRunner::Runner.new(:test_dir => PrototypeHelper::TMP_DIR) - - Dir[File.join(PrototypeHelper::TMP_DIR, '*_test.html')].each do |file| - file = File.basename(file) - test = file.sub('_test.html', '') - unless tests_to_run && !tests_to_run.include?(test) - runner.add_test(file, testcases) - end - end - - UnittestJS::Browser::SUPPORTED.each do |browser| - unless browsers_to_test && !browsers_to_test.include?(browser) - runner.add_browser(browser.to_sym) - end - end - - trap('INT') { runner.teardown; exit } - runner.run - end - - task :build => [:clean, :dist] do - builder = UnittestJS::Builder::SuiteBuilder.new({ - :input_dir => PrototypeHelper::TEST_UNIT_DIR, - :assets_dir => PrototypeHelper::DIST_DIR - }) - selected_tests = (ENV['TESTS'] || '').split(',') - builder.collect(*selected_tests) - builder.render - end - - task :clean => [:require] do - UnittestJS::Builder.empty_dir!(PrototypeHelper::TMP_DIR) - end - - task :require do - PrototypeHelper.require_unittest_js - end - - desc "Builds all the unit tests and starts the server. (The user can visit the tests manually in a browser at their leisure.)" - task :server => [:build] do - runner = UnittestJS::WEBrickRunner::Runner.new(:test_dir => PrototypeHelper::TMP_DIR) - testcases = ENV['TESTCASES'] - - Dir[File.join(PrototypeHelper::TMP_DIR, '*_test.html')].each do |file| - file = File.basename(file) - test = file.sub('_test.html', '') - runner.add_test(file, testcases) - end - - trap('INT') do - puts "...server stopped." - runner.teardown - exit - end - - puts "Server started..." - - runner.setup - - loop do - sleep 1 - end - end -end - -task :test_units do - puts '"rake test_units" is deprecated. Please use "rake test" instead.' -end - -task :build_unit_tests do - puts '"rake test_units" is deprecated. Please use "rake test:build" instead.' -end - -task :clean_tmp do - puts '"rake clean_tmp" is deprecated. Please use "rake test:clean" instead.' -end - -namespace :test_new do desc 'Starts the test server.' task :start => [:require] do - path_to_app = File.join(PrototypeHelper::ROOT_DIR, 'test.new', 'server.rb') + path_to_app = File.join(PrototypeHelper::ROOT_DIR, 'test', 'unit', 'server.rb') require path_to_app - puts "Unit tests available at " + puts "Starting unit test server..." + puts "Unit tests available at \n\n" UnitTests.run! end @@ -377,7 +295,7 @@ namespace :test_new do desc "Opens the test suite in several different browsers. (Does not start or stop the server; you should do that separately.)" task :run => [:require] do browsers, tests, grep = ENV['BROWSERS'], ENV['TESTS'], ENV['GREP'] - path_to_runner = File.join(PrototypeHelper::ROOT_DIR, 'test.new', 'runner.rb') + path_to_runner = File.join(PrototypeHelper::ROOT_DIR, 'test', 'unit', 'runner.rb') require path_to_runner Runner::run(browsers, tests, grep) @@ -390,28 +308,4 @@ namespace :test_new do url << "?grep=#{grep}" if grep system(%Q[phantomjs ./test.new/phantomjs/mocha-phantomjs.js "#{url}"]) end - end - -namespace :caja do - task :test => ['test:build', 'test:run'] - - namespace :test do - task :run => ['rake:test:run'] - - task :build => [:require, 'rake:test:clean', :dist] do - builder = UnittestJS::CajaBuilder::SuiteBuilder.new({ - :input_dir => PrototypeHelper::TEST_UNIT_DIR, - :assets_dir => PrototypeHelper::DIST_DIR, - :whitelist_dir => File.join(PrototypeHelper::TEST_DIR, 'unit', 'caja_whitelists'), - :html_attrib_schema => 'html_attrib.json' - }) - selected_tests = (ENV['TESTS'] || '').split(',') - builder.collect(*selected_tests) - builder.render - end - end - task :require => ['rake:test:require'] do - PrototypeHelper.require_caja_builder - end -end \ No newline at end of file diff --git a/test/unit/server.rb b/test/unit/server.rb index 78c09b3a8..8e5517830 100644 --- a/test/unit/server.rb +++ b/test/unit/server.rb @@ -17,7 +17,7 @@ class UnitTests < Sinatra::Application # we can start the server on one machine and then run tests from another. set :bind, '0.0.0.0' - PATH_TO_PROTOTYPE = PWD.join('..', 'dist', 'prototype.js') + PATH_TO_PROTOTYPE = PWD.join('..', '..', 'dist', 'prototype.js') unless PATH_TO_PROTOTYPE.file? raise "You must run `rake dist` before starting the server." From 6a021d771a4ffd8a0f6a742e6821b3b2a4993be3 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 5 Nov 2014 21:49:20 -0600 Subject: [PATCH 464/502] Some comments and cleanup. --- test/unit/server.rb | 9 ++++----- test/unit/static/js/test_helpers.js | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/test/unit/server.rb b/test/unit/server.rb index 8e5517830..be957102b 100644 --- a/test/unit/server.rb +++ b/test/unit/server.rb @@ -2,8 +2,6 @@ require 'pathname' require 'json' -require 'pp' - class UnitTests < Sinatra::Application PWD = Pathname.new( File.expand_path( File.dirname(__FILE__) ) ) @@ -66,6 +64,8 @@ def self.get_or_post(url, &block) redirect to("/test/#{params[:names]}") end + # /test/ will run all tests; + # /test/foo,bar will run just "foo" and "bar" tests. get '/test/:names?' do names = params[:names] @suites = names.nil? ? SUITES : names.split(/,/).uniq @@ -73,6 +73,8 @@ def self.get_or_post(url, &block) erb :tests, :locals => { :suites => @suites } end + # Will read from disk each time. No server restart necessary when the + # distributable is updated. get '/prototype.js' do content_type 'text/javascript' send_file PATH_TO_PROTOTYPE @@ -83,7 +85,6 @@ def self.get_or_post(url, &block) # (a) they should be more prominent in the directory structure; # (b) they should never, ever get cached, and we want to enforce that # aggressively. - get '/js/tests/:filename' do filename = params[:filename] path = PATH_TO_TEST_JS.join(filename) @@ -110,8 +111,6 @@ def self.get_or_post(url, &block) :body => request.body.read } - pp response[:headers] - content_type 'application/json' JSON.dump(response) end diff --git a/test/unit/static/js/test_helpers.js b/test/unit/static/js/test_helpers.js index 01857a9b2..f4c04218f 100644 --- a/test/unit/static/js/test_helpers.js +++ b/test/unit/static/js/test_helpers.js @@ -1,3 +1,6 @@ +// TODO: Ideally, none of the stuff in this file should use Prototype, so +// that a broken method inside Prototype does not end up affecting the test +// reporting. (function () { // Needed because Mocha's HTML test runner assumes the presence of From 27e05e3e5787c3bda722eb26d999a4c1462bb847 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 5 Nov 2014 21:49:46 -0600 Subject: [PATCH 465/502] Make `PrototypeHelper.require_phantomjs` work properly on Windows. --- Rakefile | 71 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/Rakefile b/Rakefile index e0708b6b8..95696c51c 100755 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,11 @@ require 'rake' require 'rake/packagetask' +require 'rbconfig' require 'yaml' module PrototypeHelper extend Rake::DSL - + ROOT_DIR = File.expand_path(File.dirname(__FILE__)) SRC_DIR = File.join(ROOT_DIR, 'src') DIST_DIR = File.join(ROOT_DIR, 'dist') @@ -15,9 +16,12 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] - + DEFAULT_SELECTOR_ENGINE = 'sizzle' - + + host = RbConfig::CONFIG['host'] + IS_WINDOWS = host.include?('mswin') || host.include?('mingw32') + # Possible options for PDoc syntax highlighting, in order of preference. SYNTAX_HIGHLIGHTERS = [:pygments, :coderay, :none] @@ -33,7 +37,7 @@ module PrototypeHelper return false end end - + def self.require_git return if has_git? puts "\nPrototype requires Git in order to load its dependencies." @@ -42,30 +46,30 @@ module PrototypeHelper puts " http://book.git-scm.com/2_installing_git.html" exit end - + def self.sprocketize(options = {}) options = { :destination => File.join(DIST_DIR, options[:source]), :strip_comments => true }.merge(options) - + require_sprockets load_path = [SRC_DIR] - + if selector_path = get_selector_engine(options[:selector_engine]) load_path << selector_path end - + secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, options[:path]), :load_path => load_path, :source_files => [options[:source]], :strip_comments => options[:strip_comments] ) - + secretary.concatenation.save_to(options[:destination]) end - + def self.build_doc_for(file) rm_rf(DOC_DIR) mkdir_p(DOC_DIR) @@ -98,7 +102,7 @@ EOF :assets => 'doc_assets' }) end - + def self.require_package(name) begin require name @@ -108,26 +112,27 @@ EOF exit end end - + def self.require_phantomjs - success = system("phantomjs -v > /dev/null 2>&1") + cmd = IS_WINDOWS ? "phantomjs.cmd -v" : "phantomjs -v > /dev/null 2>&1" + success = system(cmd) if !success puts "\nYou need phantomjs installed to run this task. Find out how at:" puts " http://phantomjs.org/download.html" exit end end - + def self.syntax_highlighter if ENV['SYNTAX_HIGHLIGHTER'] highlighter = ENV['SYNTAX_HIGHLIGHTER'].to_sym require_highlighter(highlighter, true) return highlighter end - + SYNTAX_HIGHLIGHTERS.detect { |n| require_highlighter(n) } end - + def self.require_highlighter(name, verbose=false) case name when :pygments @@ -160,30 +165,30 @@ EOF exit end end - + def self.require_sprockets require_submodule('Sprockets', 'sprockets') end - + def self.require_pdoc require_submodule('PDoc', 'pdoc') end - + def self.require_unittest_js require_submodule('UnittestJS', 'unittest_js') end - + def self.require_caja_builder require_submodule('CajaBuilder', 'caja_builder') end - + def self.get_selector_engine(name) return if !name # If the submodule exists, we should use it. submodule_path = File.join(ROOT_DIR, "vendor", name) return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) return submodule_path if name === "legacy_selector" - + # If it doesn't exist, we should fetch it. get_submodule('the required selector engine', "#{name}/repository") unless File.exist?(submodule_path) @@ -191,11 +196,11 @@ EOF exit end end - + def self.get_submodule(name, path) require_git puts "\nYou seem to be missing #{name}. Obtaining it via git...\n\n" - + Kernel.system("git submodule init") return true if Kernel.system("git submodule update vendor/#{path}") # If we got this far, something went wrong. @@ -204,7 +209,7 @@ EOF puts " $ git submodule update vendor/#{path}" false end - + def self.require_submodule(name, path) begin require path @@ -225,7 +230,7 @@ EOF exit end end - + def self.current_head `git show-ref --hash HEAD`.chomp[0..6] end @@ -247,7 +252,7 @@ namespace :doc do task :build => [:require] do PrototypeHelper.build_doc_for(ENV['SECTION'] ? "#{ENV['SECTION']}.js" : 'prototype.js') end - + task :require do PrototypeHelper.require_pdoc end @@ -282,30 +287,30 @@ namespace :test do task :start => [:require] do path_to_app = File.join(PrototypeHelper::ROOT_DIR, 'test', 'unit', 'server.rb') require path_to_app - + puts "Starting unit test server..." puts "Unit tests available at \n\n" UnitTests.run! end - + task :require do PrototypeHelper.require_package('sinatra') end - + desc "Opens the test suite in several different browsers. (Does not start or stop the server; you should do that separately.)" task :run => [:require] do browsers, tests, grep = ENV['BROWSERS'], ENV['TESTS'], ENV['GREP'] path_to_runner = File.join(PrototypeHelper::ROOT_DIR, 'test', 'unit', 'runner.rb') require path_to_runner - + Runner::run(browsers, tests, grep) end - + task :phantom => [:require] do PrototypeHelper.require_phantomjs tests, grep = ENV['TESTS'], ENV['GREP'] url = "http://127.0.0.1:4567/test/#{tests}" url << "?grep=#{grep}" if grep - system(%Q[phantomjs ./test.new/phantomjs/mocha-phantomjs.js "#{url}"]) + system(%Q[phantomjs ./test/unit/phantomjs/mocha-phantomjs.js "#{url}"]) end end From 83089d029da84f1c056a77d3e1835ef27577d097 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Wed, 5 Nov 2014 22:37:59 -0600 Subject: [PATCH 466/502] Fix spurious test failures in PhantomJS. --- test/unit/views/layout.erb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unit/views/layout.erb b/test/unit/views/layout.erb index 663c6f00b..f2c48e34c 100644 --- a/test/unit/views/layout.erb +++ b/test/unit/views/layout.erb @@ -100,15 +100,6 @@
    '); * // -> Element (and prints "updated!" in an alert dialog). * $('fruits').innerHTML; * // -> '

    Kiwi, banana and apple.

    ' - * + * * Relying on the `toString()` method: - * + * * $('fruits').update(123); * // -> Element * $('fruits').innerHTML; * // -> '123' - * + * * Finally, you can do some pretty funky stuff by defining your own * `toString()` method on your custom objects: - * + * * var Fruit = Class.create({ * initialize: function(fruit){ * this.fruit = fruit; * }, * toString: function(){ - * return 'I am a fruit and my name is "' + this.fruit + '".'; + * return 'I am a fruit and my name is "' + this.fruit + '".'; * } * }); * var apple = new Fruit('apple'); - * + * * $('fruits').update(apple); * $('fruits').innerHTML; * // -> 'I am a fruit and my name is "apple".' **/ function update(element, content) { element = $(element); - + // Purge the element's existing contents of all storage keys and // event listeners, since said content will be replaced no matter // what. var descendants = element.getElementsByTagName('*'), i = descendants.length; while (i--) purgeElement(descendants[i]); - + if (content && content.toElement) content = content.toElement(); - + if (Object.isElement(content)) return element.update().insert(content); - - + + content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); - + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { // Scripts are not evaluated when updating a SCRIPT element. element.text = content; return element; } - + if (ANY_INNERHTML_BUGGY) { if (tagName in INSERTION_TRANSLATIONS.tags) { while (element.firstChild) element.removeChild(element.firstChild); - - var nodes = getContentFromAnonymousElement(tagName, content.stripScripts()); + + var nodes = getContentFromAnonymousElement(tagName, content.stripScripts()); for (var i = 0, node; node = nodes[i]; i++) element.appendChild(node); - + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { // IE barfs when inserting a string that beings with a LINK // element. The workaround is to add any content to the beginning @@ -675,10 +675,10 @@ // getContentFromAnonymousElement below). while (element.firstChild) element.removeChild(element.firstChild); - + var nodes = getContentFromAnonymousElement(tagName, content.stripScripts(), true); - + for (var i = 0, node; node = nodes[i]; i++) element.appendChild(node); } else { @@ -687,11 +687,11 @@ } else { element.innerHTML = content.stripScripts(); } - + content.evalScripts.bind(content).defer(); return element; } - + /** * Element.replace(@element[, newContent]) -> Element * @@ -702,53 +702,53 @@ * * `newContent` can be either plain text, an HTML snippet or any JavaScript * object which has a `toString()` method. - * + * * If `newContent` contains any `'); * // -> Element (ul#favorite) and prints "removed!" in an alert dialog. - * + * * $('fruits').innerHTML; * // -> '

    Melon, oranges and grapes.

    ' - * + * * With plain text: - * + * * $('still-first').replace('Melon, oranges and grapes.'); * // -> Element (p#still-first) * * $('fruits').innerHTML; * // -> 'Melon, oranges and grapes.' - * + * * Finally, relying on the `toString()` method: - * + * * $('fruits').replace(123); * // -> Element - * + * * $('food').innerHTML; * // -> '123' * @@ -760,13 +760,13 @@ * as part of [Web Forms 2](http://www.whatwg.org/specs/web-forms/current-work/). * As a workaround, use the generic version instead * (`Element.replace('foo', '

    Bar

    ')`). - * + * **/ function replace(element, content) { element = $(element); - + if (content && content.toElement) { - content = content.toElement(); + content = content.toElement(); } else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); @@ -774,11 +774,11 @@ content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } - + element.parentNode.replaceChild(content, element); return element; } - + var INSERTION_TRANSLATIONS = { before: function(element, node) { element.parentNode.insertBefore(node, element); @@ -792,7 +792,7 @@ after: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, - + tags: { TABLE: ['', '
    ', 1], TBODY: ['', '
    ', 2], @@ -801,15 +801,15 @@ SELECT: ['', 1] } }; - + var tags = INSERTION_TRANSLATIONS.tags; - + Object.extend(tags, { THEAD: tags.TBODY, TFOOT: tags.TBODY, TH: tags.TD }); - + function replace_IE(element, content) { element = $(element); if (content && content.toElement) @@ -818,71 +818,71 @@ element.parentNode.replaceChild(content, element); return element; } - + content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - + if (tagName in INSERTION_TRANSLATIONS.tags) { var nextSibling = Element.next(element); var fragments = getContentFromAnonymousElement( tagName, content.stripScripts()); - + parent.removeChild(element); - + var iterator; if (nextSibling) iterator = function(node) { parent.insertBefore(node, nextSibling) }; else iterator = function(node) { parent.appendChild(node); } - + fragments.each(iterator); } else { // We don't need to special-case this one. element.outerHTML = content.stripScripts(); } - + content.evalScripts.bind(content).defer(); return element; } - + if ('outerHTML' in document.documentElement) replace = replace_IE; - + function isContent(content) { if (Object.isUndefined(content) || content === null) return false; - + if (Object.isString(content) || Object.isNumber(content)) return true; - if (Object.isElement(content)) return true; + if (Object.isElement(content)) return true; if (content.toElement || content.toHTML) return true; - + return false; } - + // This private method does the bulk of the work for Element#insert. The // actual insert method handles argument normalization and multiple // content insertions. function insertContentAt(element, content, position) { position = position.toLowerCase(); var method = INSERTION_TRANSLATIONS[position]; - + if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { method(element, content); return element; } - - content = Object.toHTML(content); + + content = Object.toHTML(content); var tagName = ((position === 'before' || position === 'after') ? element.parentNode : element).tagName.toUpperCase(); - + var childNodes = getContentFromAnonymousElement(tagName, content.stripScripts()); - + if (position === 'top' || position === 'after') childNodes.reverse(); - + for (var i = 0, node; node = childNodes[i]; i++) method(element, node); - - content.evalScripts.bind(content).defer(); + + content.evalScripts.bind(content).defer(); } /** @@ -932,16 +932,16 @@ **/ function insert(element, insertions) { element = $(element); - + if (isContent(insertions)) insertions = { bottom: insertions }; - + for (var position in insertions) insertContentAt(element, insertions[position], position); - - return element; + + return element; } - + /** * Element.wrap(@element, wrapper[, attributes]) -> Element * - wrapper (Element | String): An element to wrap `element` inside, or @@ -950,19 +950,19 @@ * element. Refer to the [[Element]] constructor for usage. * * Wraps an element inside another, then returns the wrapper. - * + * * If the given element exists on the page, [[Element.wrap]] will wrap it in * place — its position will remain the same. - * + * * The `wrapper` argument can be _either_ an existing [[Element]] _or_ a * string representing the tag name of an element to be created. The optional * `attributes` argument can contain a list of attribute/value pairs that * will be set on the wrapper using [[Element.writeAttribute]]. - * + * * ##### Examples - * + * * Original HTML: - * + * * language: html * * @@ -974,20 +974,20 @@ * * *
    2
    - * + * * JavaScript: - * + * * // approach 1: * var div = new Element('div', { 'class': 'table-wrapper' }); * $('data').wrap(div); - * + * * // approach 2: * $('data').wrap('div', { 'class': 'table-wrapper' }); - * + * * // Both examples are equivalent — they return the DIV. - * + * * Resulting HTML: - * + * * language: html *
    * @@ -1000,8 +1000,8 @@ * * *
    2
    - *
    - * + *
    + * * ##### Warning * * Using [[Element.wrap]] as an instance method (e.g., `$('foo').wrap('p')`) @@ -1012,10 +1012,10 @@ **/ function wrap(element, wrapper, attributes) { element = $(element); - + if (Object.isElement(wrapper)) { // The wrapper argument is a DOM node. - $(wrapper).writeAttribute(attributes || {}); + $(wrapper).writeAttribute(attributes || {}); } else if (Object.isString(wrapper)) { // The wrapper argument is a string representing a tag name. wrapper = new Element(wrapper, attributes); @@ -1024,15 +1024,15 @@ // of attributes. wrapper = new Element('div', wrapper); } - + if (element.parentNode) element.parentNode.replaceChild(wrapper, element); - + wrapper.appendChild(element); - + return wrapper; } - + /** * Element.cleanWhitespace(@element) -> Element * @@ -1078,7 +1078,7 @@ function cleanWhitespace(element) { element = $(element); var node = element.firstChild; - + while (node) { var nextNode = node.nextSibling; if (node.nodeType === Node.TEXT_NODE && !/\S/.test(node.nodeValue)) @@ -1087,17 +1087,17 @@ } return element; } - + /** * Element.empty(@element) -> Element * * Tests whether `element` is empty (i.e., contains only whitespace). - * + * * ##### Examples - * + * *
    *
    full!
    - * + * * $('wallet').empty(); * // -> true * @@ -1107,20 +1107,20 @@ function empty(element) { return $(element).innerHTML.blank(); } - + // In older versions of Internet Explorer, certain elements don't like // having innerHTML set on them — including SELECT and most table-related // tags. So we wrap the string with enclosing HTML (if necessary), stick it // in a DIV, then grab the DOM nodes. function getContentFromAnonymousElement(tagName, html, force) { var t = INSERTION_TRANSLATIONS.tags[tagName], div = DIV; - + var workaround = !!t; if (!workaround && force) { workaround = true; t = ['', '', 0]; } - + if (workaround) { div.innerHTML = ' ' + t[0] + html + t[1]; div.removeChild(div.firstChild); @@ -1129,11 +1129,11 @@ } else { div.innerHTML = html; } - + return $A(div.childNodes); //return SLICE.call(div.childNodes, 0); } - + /** * Element.clone(@element, deep) -> Element * - deep (Boolean): Whether to clone `element`'s descendants as well. @@ -1142,19 +1142,19 @@ * * A wrapper around DOM Level 2 `Node#cloneNode`, [[Element.clone]] cleans up * any expando properties defined by Prototype. - * + * * ##### Example - * + * *
    *
    *
    - * + * * var clone = $('original').clone(); * clone.className; * // -> "original" * clone.childElements(); * // -> [] - * + * * var deepClone = $('original').clone(true); * deepClone.className; * // -> "original" @@ -1175,7 +1175,7 @@ } return Element.extend(clone); } - + // Performs cleanup on a single element before it is removed from the page. function purgeElement(element) { var uid = getUniqueElementID(element); @@ -1186,13 +1186,13 @@ delete Element.Storage[uid]; } } - + function purgeCollection(elements) { var i = elements.length; while (i--) purgeElement(elements[i]); } - + function purgeCollection_IE(elements) { var i = elements.length, element, uid; while (i--) { @@ -1202,31 +1202,31 @@ delete Event.cache[uid]; } } - + if (HAS_UNIQUE_ID_PROPERTY) { purgeCollection = purgeCollection_IE; } - - + + /** * Element.purge(@element) -> null - * + * * Removes all event listeners and storage keys from an element. - * + * * To be used just before removing an element from the page. **/ function purge(element) { if (!(element = $(element))) return; purgeElement(element); - + var descendants = element.getElementsByTagName('*'), i = descendants.length; - + while (i--) purgeElement(descendants[i]); - + return null; } - + Object.extend(methods, { remove: remove, update: update, @@ -1238,10 +1238,10 @@ clone: clone, purge: purge }); - + // TRAVERSAL - + /** * Element.recursivelyCollect(@element, property) -> [Element...] * @@ -1258,12 +1258,12 @@ * convenient way to grab elements, so directly accessing * [[Element.recursivelyCollect]] should seldom be needed. However, if you * are after something out of the ordinary, it is the way to go. - * + * * Note that all of Prototype's DOM traversal methods ignore text nodes and * return element nodes only. - * + * * ##### Examples - * + * * language: html *
      *
    • @@ -1285,17 +1285,17 @@ element = $(element); maximumLength = maximumLength || -1; var elements = []; - + while (element = element[property]) { if (element.nodeType === Node.ELEMENT_NODE) elements.push(Element.extend(element)); - + if (elements.length === maximumLength) break; } - - return elements; + + return elements; } - + /** * Element.ancestors(@element) -> [Element...] @@ -1332,7 +1332,7 @@ function ancestors(element) { return recursivelyCollect(element, 'parentNode'); } - + /** * Element.descendants(@element) -> [Element...] * @@ -1344,7 +1344,7 @@ function descendants(element) { return Element.select(element, '*'); } - + /** * Element.firstDescendant(@element) -> Element * @@ -1354,7 +1354,7 @@ * any node, including text nodes and comment nodes. * * ##### Examples - * + * * language: html *
      *
      @@ -1367,11 +1367,11 @@ * * $('australopithecus').firstDescendant(); * // -> div#homo-erectus - * + * * // the DOM property returns any first node * $('homo-erectus').firstChild; * // -> comment node "Latin is super" - * + * * // this is what we want! * $('homo-erectus').firstDescendant(); * // -> div#homo-neanderthalensis @@ -1383,7 +1383,7 @@ return $(element); } - + /** deprecated, alias of: Element.childElements * Element.immediateDescendants(@element) -> [Element...] * @@ -1391,37 +1391,37 @@ **/ function immediateDescendants(element) { var results = [], child = $(element).firstChild; - + while (child) { if (child.nodeType === Node.ELEMENT_NODE) results.push(Element.extend(child)); - + child = child.nextSibling; } - + return results; } - + /** * Element.previousSiblings(@element) -> [Element...] * * Collects all of `element`'s previous siblings and returns them as an * [[Array]] of elements. - * + * * Two elements are siblings if they have the same parent. So for example, * the `` and `` elements are siblings (their parent is the * `` element). Previous-siblings are simply the ones which precede * `element` in the document. - * + * * The returned [[Array]] reflects the siblings _inversed_ order in the * document (e.g. an index of 0 refers to the lowest sibling i.e., the one * closest to `element`). - * + * * Note that all of Prototype's DOM traversal methods ignore text nodes and * return element nodes only. - * + * * ##### Examples - * + * * language: html *
        *
      • Golden Delicious
      • @@ -1434,33 +1434,33 @@ * * $('mcintosh').previousSiblings(); * // -> [li#mutsu, li#golden-delicious] - * + * * $('golden-delicious').previousSiblings(); * // -> [] **/ function previousSiblings(element) { return recursivelyCollect(element, 'previousSibling'); } - + /** * Element.nextSiblings(@element) -> [Element...] * * Collects all of `element`'s next siblings and returns them as an [[Array]] * of elements. - * + * * Two elements are siblings if they have the same parent. So for example, * the `head` and `body` elements are siblings (their parent is the `html` * element). Next-siblings are simply the ones which follow `element` in the * document. - * + * * The returned [[Array]] reflects the siblings order in the document * (e.g. an index of 0 refers to the sibling right below `element`). - * + * * Note that all of Prototype's DOM traversal methods ignore text nodes and * return element nodes only. - * + * * ##### Examples - * + * * language: html *
          *
        • Golden Delicious
        • @@ -1473,14 +1473,14 @@ * * $('mutsu').nextSiblings(); * // -> [li#mcintosh, li#ida-red] - * + * * $('ida-red').nextSiblings(); * // -> [] **/ function nextSiblings(element) { return recursivelyCollect(element, 'nextSibling'); } - + /** * Element.siblings(@element) -> [Element...] * @@ -1490,15 +1490,15 @@ * Two elements are siblings if they have the same parent. So for example, * the `head` and `body` elements are siblings (their parent is the `html` * element). - * + * * The returned [[Array]] reflects the siblings' order in the document (e.g. * an index of 0 refers to `element`'s topmost sibling). - * + * * Note that all of Prototype's DOM traversal methods ignore text nodes and * return element nodes only. - * + * * ##### Examples - * + * * language: html *
            *
          • Golden Delicious
          • @@ -1513,12 +1513,12 @@ * // -> [li#golden-delicious, li#mcintosh, li#ida-red] **/ function siblings(element) { - element = $(element); + element = $(element); var previous = previousSiblings(element), next = nextSiblings(element); return previous.reverse().concat(next); } - + /** * Element.match(@element, selector) -> boolean * - selector (String): A CSS selector. @@ -1526,7 +1526,7 @@ * Checks if `element` matches the given CSS selector. * * ##### Examples - * + * * language: html *
              *
            • @@ -1543,26 +1543,26 @@ * * $('fruits').match('ul'); * // -> true - * + * * $('mcintosh').match('li#mcintosh.yummy'); * // -> true - * + * * $('fruits').match('p'); * // -> false **/ function match(element, selector) { element = $(element); - + // If selector is a string, we assume it's a CSS selector. if (Object.isString(selector)) return Prototype.Selector.match(element, selector); - + // Otherwise, we assume it's an object with its own `match` method. return selector.match(element); } - - - // Internal method for optimizing traversal. Works like + + + // Internal method for optimizing traversal. Works like // `recursivelyCollect`, except it stops at the first match and doesn't // extend any elements except for the returned element. function _recursivelyFind(element, property, expression, index) { @@ -1570,7 +1570,7 @@ if (Object.isNumber(expression)) { index = expression, expression = null; } - + while (element = element[property]) { // Skip any non-element nodes. if (element.nodeType !== 1) continue; @@ -1579,12 +1579,12 @@ continue; // Skip the first `index` matches we find. if (--index >= 0) continue; - + return Element.extend(element); } } - - + + /** * Element.up(@element[, expression[, index = 0]]) -> Element * Element.up(@element[, index = 0]) -> Element @@ -1599,43 +1599,43 @@ * * The [[Element.up]] method is part of Prototype's ultimate DOM traversal * toolkit (check out [[Element.down]], [[Element.next]] and - * [[Element.previous]] for some more Prototypish niceness). It allows + * [[Element.previous]] for some more Prototypish niceness). It allows * precise index-based and/or CSS rule-based selection of any of `element`'s * **ancestors**. - * + * * As it totally ignores text nodes (it only returns elements), you don't * have to worry about whitespace nodes. - * + * * And as an added bonus, all elements returned are already extended * (see [[Element.extended]]) allowing chaining: - * + * * $(element).up(1).next('li', 2).hide(); - * + * * Walking the DOM has never been that easy! - * + * * ##### Arguments - * + * * If no arguments are passed, `element`'s first ancestor is returned (this * is similar to calling `parentNode` except [[Element.up]] returns an already * extended element. - * + * * If `index` is defined, `element`'s corresponding ancestor is returned. * (This is equivalent to selecting an element from the array of elements * returned by the method [[Element.ancestors]]). Note that the first element * has an index of 0. - * + * * If `expression` is defined, [[Element.up]] will return the first ancestor * that matches it. - * + * * If both `expression` and `index` are defined, [[Element.up]] will collect * all the ancestors matching the given CSS expression and will return the * one at the specified index. - * + * * **In all of the above cases, if no descendant is found,** `undefined` * **will be returned.** - * + * * ### Examples - * + * * language: html * * [...] @@ -1661,12 +1661,12 @@ * // -> body * * Get the third ancestor of "#mutsu": - * + * * $('mutsu').up(2); * // -> ul#fruits * * Get the first ancestor of "#mutsu" with the node name "li": - * + * * $('mutsu').up('li'); * // -> li#apples * @@ -1677,12 +1677,12 @@ * // -> li#apples * * Get the second ancestor of "#mutsu" with the node name "ul": - * + * * $('mutsu').up('ul', 1); * // -> ul#fruits * * Try to get the first ancestor of "#mutsu" with the node name "div": - * + * * $('mutsu').up('div'); * // -> undefined **/ @@ -1708,43 +1708,43 @@ * The [[Element.down]] method is part of Prototype's ultimate DOM traversal * toolkit (check out [[Element.up]], [[Element.next]] and * [[Element.previous]] for some more Prototypish niceness). It allows - * precise index-based and/or CSS rule-based selection of any of the + * precise index-based and/or CSS rule-based selection of any of the * element's **descendants**. - * + * * As it totally ignores text nodes (it only returns elements), you don't * have to worry about whitespace nodes. - * + * * And as an added bonus, all elements returned are already extended * (see [[Element.extend]]) allowing chaining: - * + * * $(element).down(1).next('li', 2).hide(); - * + * * Walking the DOM has never been that easy! - * + * * ##### Arguments - * + * * If no arguments are passed, `element`'s first descendant is returned (this * is similar to calling `firstChild` except [[Element.down]] returns an * extended element. - * + * * If `index` is defined, `element`'s corresponding descendant is returned. * (This is equivalent to selecting an element from the array of elements * returned by the method [[Element.descendants]].) Note that the first * element has an index of 0. - * + * * If `expression` is defined, [[Element.down]] will return the first * descendant that matches it. This is a great way to grab the first item in * a list for example (just pass in 'li' as the method's first argument). - * + * * If both `expression` and `index` are defined, [[Element.down]] will collect * all the descendants matching the given CSS expression and will return the * one at the specified index. - * + * * **In all of the above cases, if no descendant is found,** `undefined` * **will be returned.** - * + * * ##### Examples - * + * * language: html *
                *
              • @@ -1756,7 +1756,7 @@ *
              *
            • *
            - * + * * Get the first descendant of "#fruites": * * $('fruits').down(); @@ -1765,10 +1765,10 @@ * // -> li#apples * * Get the third descendant of "#fruits": - * + * * $('fruits').down(3); * // -> li#golden-delicious - * + * * Get the first descendant of "#apples" with the node name "li": * * $('apples').down('li'); @@ -1793,10 +1793,10 @@ function down(element, expression, index) { if (arguments.length === 1) return firstDescendant(element); element = $(element), expression = expression || 0, index = index || 0; - + if (Object.isNumber(expression)) index = expression, expression = '*'; - + var node = Prototype.Selector.select(expression, element)[index]; return Element.extend(node); } @@ -1820,40 +1820,40 @@ * **previous siblings**. (Note that two elements are considered siblings if * they have the same parent, so for example, the `head` and `body` elements * are siblings—their parent is the `html` element.) - * + * * As it totally ignores text nodes (it only returns elements), you don't * have to worry about whitespace nodes. - * - * And as an added bonus, all elements returned are already extended (see + * + * And as an added bonus, all elements returned are already extended (see * [[Element.extend]]) allowing chaining: - * + * * $(element).down('p').previous('ul', 2).hide(); - * + * * Walking the DOM has never been that easy! - * + * * ##### Arguments - * + * * If no arguments are passed, `element`'s previous sibling is returned * (this is similar as calling `previousSibling` except [[Element.previous]] * returns an already extended element). - * + * * If `index` is defined, `element`'s corresponding previous sibling is * returned. (This is equivalent to selecting an element from the array of * elements returned by the method [[Element.previousSiblings]]). Note that * the sibling _right above_ `element` has an index of 0. - * + * * If `expression` is defined, [[Element.previous]] will return the `element` * first previous sibling that matches it. - * + * * If both `expression` and `index` are defined, [[Element.previous]] will * collect all of `element`'s previous siblings matching the given CSS * expression and will return the one at the specified index. - * + * * **In all of the above cases, if no previous sibling is found,** * `undefined` **will be returned.** - * + * * ##### Examples - * + * * language: html *
              *
            • @@ -1864,12 +1864,12 @@ *
            • McIntosh
            • *
            • Ida Red
            • *
            - *

            An apple a day keeps the doctor away.

            + *

            An apple a day keeps the doctor away.

            * *
          - * + * * Get the first previous sibling of "#saying": - * + * * $('saying').previous(); * // or: * $('saying').previous(0); @@ -1896,14 +1896,14 @@ * // -> li#golden-delicious * * Try to get the sixth previous sibling of "#ida-red": - * + * * $('ida-red').previous(5); * // -> undefined **/ function previous(element, expression, index) { return _recursivelyFind(element, 'previousSibling', expression, index); } - + /** * Element.next(@element[, expression[, index = 0]]) -> Element * Element.next(@element[, index = 0]) -> Element @@ -1923,40 +1923,40 @@ * `element`'s **following siblings**. (Note that two elements are considered * siblings if they have the same parent, so for example, the `head` and * `body` elements are siblings—their parent is the `html` element.) - * + * * As it totally ignores text nodes (it only returns elements), you don't * have to worry about whitespace nodes. - * - * And as an added bonus, all elements returned are already extended (see + * + * And as an added bonus, all elements returned are already extended (see * [[Element.extend]]) allowing chaining: - * + * * $(element).down(1).next('li', 2).hide(); - * + * * Walking the DOM has never been that easy! - * + * * ##### Arguments - * + * * If no arguments are passed, `element`'s following sibling is returned * (this is similar as calling `nextSibling` except [[Element.next]] returns an * already extended element). - * + * * If `index` is defined, `element`'s corresponding following sibling is * returned. (This is equivalent to selecting an element from the array of * elements returned by the method [[Element.nextSiblings]]). Note that the * sibling _right below_ `element` has an index of 0. - * + * * If `expression` is defined, [[Element.next]] will return the `element` first * following sibling that matches it. - * + * * If both `expression` and `index` are defined, [[Element.next]] will collect * all of `element`'s following siblings matching the given CSS expression * and will return the one at the specified index. - * + * * **In all of the above cases, if no following sibling is found,** * `undefined` **will be returned.** - * + * * ##### Examples - * + * * language: html *
            *
          • @@ -1967,12 +1967,12 @@ *
          • McIntosh
          • *
          • Ida Red
          • *
          - *

          An apple a day keeps the doctor away.

          + *

          An apple a day keeps the doctor away.

          * *
        * * Get the first sibling after "#title": - * + * * $('title').next(); * // or: * $('title').next(0); @@ -1989,7 +1989,7 @@ * // -> p#sayings * * Get the first sibling after "#golden-delicious" with class name "yummy": - * + * * $('golden-delicious').next('.yummy'); * // -> li#mcintosh * @@ -2001,12 +2001,12 @@ * Try to get the first sibling after "#ida-red": * * $('ida-red').next(); - * // -> undefined + * // -> undefined **/ function next(element, expression, index) { return _recursivelyFind(element, 'nextSibling', expression, index); } - + /** * Element.select(@element, expression...) -> [Element...] * - expression (String): A CSS selector. @@ -2014,12 +2014,12 @@ * Takes an arbitrary number of CSS selectors and returns an array of * descendants of `element` that match any of them. * - * This method is very similar to [[$$]] but can be used within the context - * of one element, rather than the whole document. The supported CSS syntax + * This method is very similar to [[$$]] but can be used within the context + * of one element, rather than the whole document. The supported CSS syntax * is identical, so please refer to the [[$$]] docs for details. - * + * * ##### Examples - * + * * language: html *
          *
        • @@ -2030,7 +2030,7 @@ *
        • McIntosh
        • *
        • Ida Red
        • *
        - *

        An apple a day keeps the doctor away.

        + *

        An apple a day keeps the doctor away.

        * *
      * @@ -2038,28 +2038,28 @@ * * $('apples').select('[title="yummy!"]'); * // -> [h3, li#golden-delicious, li#mutsu] - * + * * $('apples').select( 'p#saying', 'li[title="yummy!"]'); * // -> [li#golden-delicious, li#mutsu, p#saying] - * + * * $('apples').select('[title="disgusting!"]'); * // -> [] - * + * * ##### Tip * * [[Element.select]] can be used as a pleasant alternative to the native * method `getElementsByTagName`: - * + * * var nodes = $A(someUL.getElementsByTagName('li')).map(Element.extend); * var nodes2 = someUL.select('li'); - * + * * In the first example, you must explicitly convert the result set to an * [[Array]] (so that Prototype's [[Enumerable]] methods can be used) and - * must manually call [[Element.extend]] on each node (so that custom - * instance methods can be used on the nodes). [[Element.select]] takes care + * must manually call [[Element.extend]] on each node (so that custom + * instance methods can be used on the nodes). [[Element.select]] takes care * of both concerns on its own. - * - * If you're using 1.6 or above (and the performance optimizations therein), + * + * If you're using 1.6 or above (and the performance optimizations therein), * the speed difference between these two examples is negligible. **/ function select(element) { @@ -2097,7 +2097,7 @@ * // -> [li#chi, li#la, li#aus] * $('nyc').adjacent('li.uk', 'li.jp'); * // -> [li#lon, li#tok] - **/ + **/ function adjacent(element) { element = $(element); var expressions = SLICE.call(arguments, 1).join(', '); @@ -2106,10 +2106,10 @@ if (Prototype.Selector.match(sibling, expressions)) results.push(sibling); } - + return results; } - + /** * Element.descendantOf(@element, ancestor) -> Boolean * - ancestor (Element | String): The element to check against (or its ID). @@ -2141,19 +2141,19 @@ if (element === ancestor) return true; return false; } - + function descendantOf_contains(element, ancestor) { element = $(element), ancestor = $(ancestor); // Some nodes, like `document`, don't have the "contains" method. if (!ancestor.contains) return descendantOf_DOM(element, ancestor); return ancestor.contains(element) && ancestor !== element; } - + function descendantOf_compareDocumentPosition(element, ancestor) { element = $(element), ancestor = $(ancestor); return (element.compareDocumentPosition(ancestor) & 8) === 8; } - + var descendantOf; if (DIV.compareDocumentPosition) { descendantOf = descendantOf_compareDocumentPosition; @@ -2162,8 +2162,8 @@ } else { descendantOf = descendantOf_DOM; } - - + + Object.extend(methods, { recursivelyCollect: recursivelyCollect, ancestors: ancestors, @@ -2181,13 +2181,13 @@ select: select, adjacent: adjacent, descendantOf: descendantOf, - + // ALIASES /** alias of: Element.select * Element.getElementsBySelector(@element, selector) -> [Element...] **/ getElementsBySelector: select, - + /** * Element.childElements(@element) -> [Element...] * @@ -2225,34 +2225,34 @@ **/ childElements: immediateDescendants }); - - + + // ATTRIBUTES /** * Element.identify(@element) -> String * * Returns `element`'s ID. If `element` does not have an ID, one is * generated, assigned to `element`, and returned. - * + * * ##### Examples - * + * * Original HTML: - * + * *
        *
      • apple
      • *
      • orange
      • *
      - * + * * JavaScript: - * + * * $('apple').identify(); * // -> 'apple' - * + * * $('apple').next().identify(); * // -> 'anonymous_element_1' - * + * * Resulting HTML: - * + * *
        *
      • apple
      • *
      • orange
      • @@ -2263,30 +2263,30 @@ element = $(element); var id = Element.readAttribute(element, 'id'); if (id) return id; - + // The element doesn't have an ID of its own. Give it one, first ensuring // that it's unique. do { id = 'anonymous_element_' + idCounter++ } while ($(id)); - + Element.writeAttribute(element, 'id', id); return id; } - + /** * Element.readAttribute(@element, attributeName) -> String | null * * Returns the value of `element`'s `attribute` or `null` if `attribute` has * not been specified. - * + * * This method serves two purposes. First it acts as a simple wrapper around * `getAttribute` which isn't a "real" function in Safari and Internet * Explorer (it doesn't have `.apply` or `.call` for instance). Secondly, it * cleans up the horrible mess Internet Explorer makes when handling * attributes. - * + * * ##### Examples - * + * * language: html * * @@ -2294,44 +2294,44 @@ * * $('tag').readAttribute('href'); * // -> '/tags/prototype' - * + * * $('tag').readAttribute('title'); * // -> 'view related bookmarks.' - * + * * $('tag').readAttribute('my_widget'); * // -> 'some info.' **/ function readAttribute(element, name) { return $(element).getAttribute(name); } - + function readAttribute_IE(element, name) { element = $(element); - + // If the attribute name exists in the value translation table, it means // we should use a custom method for retrieving that attribute's value. var table = ATTRIBUTE_TRANSLATIONS.read; if (table.values[name]) return table.values[name](element, name); - + // If it exists in the name translation table, it means the attribute has // an alias. if (table.names[name]) name = table.names[name]; - + // Special-case namespaced attributes. if (name.include(':')) { if (!element.attributes || !element.attributes[name]) return null; return element.attributes[name].value; } - + return element.getAttribute(name); } - + function readAttribute_Opera(element, name) { if (name === 'title') return element.title; return element.getAttribute(name); } - + var PROBLEMATIC_ATTRIBUTE_READING = (function() { // This test used to set 'onclick' to `Prototype.emptyFunction`, but that // caused an (uncatchable) error in IE 10. For some reason, switching to @@ -2342,14 +2342,14 @@ DIV.removeAttribute('onclick'); return isFunction; })(); - + if (PROBLEMATIC_ATTRIBUTE_READING) { readAttribute = readAttribute_IE; } else if (Prototype.Browser.Opera) { readAttribute = readAttribute_Opera; } - - + + /** * Element.writeAttribute(@element, attribute[, value = true]) -> Element * Element.writeAttribute(@element, attributes) -> Element @@ -2360,18 +2360,25 @@ function writeAttribute(element, name, value) { element = $(element); var attributes = {}, table = ATTRIBUTE_TRANSLATIONS.write; - + if (typeof name === 'object') { attributes = name; } else { attributes[name] = Object.isUndefined(value) ? true : value; } - + for (var attr in attributes) { name = table.names[attr] || attr; value = attributes[attr]; - if (table.values[attr]) - name = table.values[attr](element, value) || name; + if (table.values[attr]) { + // The value needs to be handled a certain way. Either the handler + // function will transform the value (in which case it'll return the + // new value) or it'll handle the attribute setting a different way + // altogether, in which case it won't return anything. In the latter + // case, we can skip the actual call to `setAttribute`. + value = table.values[attr](element, value); + if (Object.isUndefined(value)) continue; + } if (value === false || value === null) element.removeAttribute(name); else if (value === true) @@ -2381,7 +2388,7 @@ return element; } - + // Test whether checkboxes work properly with `hasAttribute`. var PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES = (function () { if (!HAS_EXTENDED_CREATE_ELEMENT_SYNTAX) { @@ -2394,24 +2401,24 @@ var node = checkbox.getAttributeNode('checked'); return !node || !node.specified; })(); - + function hasAttribute(element, attribute) { attribute = ATTRIBUTE_TRANSLATIONS.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return !!(node && node.specified); } - + function hasAttribute_IE(element, attribute) { if (attribute === 'checked') { return element.checked; } return hasAttribute(element, attribute); } - - GLOBAL.Element.Methods.Simulated.hasAttribute = - PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES ? + + GLOBAL.Element.Methods.Simulated.hasAttribute = + PROBLEMATIC_HAS_ATTRIBUTE_WITH_CHECKBOXES ? hasAttribute_IE : hasAttribute; - + /** deprecated * Element.classNames(@element) -> [String...] * @@ -2425,23 +2432,23 @@ function classNames(element) { return new Element.ClassNames(element); } - + var regExpCache = {}; function getRegExpForClassName(className) { if (regExpCache[className]) return regExpCache[className]; - + var re = new RegExp("(^|\\s+)" + className + "(\\s+|$)"); regExpCache[className] = re; return re; } - + /** * Element.hasClassName(@element, className) -> Boolean * * Checks for the presence of CSS class `className` on `element`. * * ##### Examples - * + * * language: html *
        * @@ -2449,23 +2456,23 @@ * * $('mutsu').hasClassName('fruit'); * // -> true - * + * * $('mutsu').hasClassName('vegetable'); * // -> false **/ function hasClassName(element, className) { if (!(element = $(element))) return; - + var elementClassName = element.className; // We test these common cases first because we'd like to avoid creating // the regular expression, if possible. if (elementClassName.length === 0) return false; if (elementClassName === className) return true; - + return getRegExpForClassName(className).test(elementClassName); } - + /** * Element.addClassName(@element, className) -> Element * - className (String): The class name to add. @@ -2489,13 +2496,13 @@ **/ function addClassName(element, className) { if (!(element = $(element))) return; - + if (!hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; - + return element; } - + /** * Element.removeClassName(@element, className) -> Element * @@ -2507,35 +2514,35 @@ * * language: html *
        - * + * * Then: * * $('mutsu').removeClassName('food'); * // -> Element - * + * * $('mutsu').className; * // -> 'apple fruit' **/ function removeClassName(element, className) { if (!(element = $(element))) return; - + element.className = element.className.replace( getRegExpForClassName(className), ' ').strip(); - + return element; } - + /** * Element.toggleClassName(@element, className[, bool]) -> Element * * Toggles the presence of CSS class `className` on `element`. - * + * * By default, `toggleClassName` will flip to the opposite state, but * will use `bool` instead if it's given; `true` will add the class name * and `false` will remove it. * * ##### Examples - * + * * language: html *
        * @@ -2543,31 +2550,31 @@ * * $('mutsu').hasClassName('fruit'); * // -> false - * + * * $('mutsu').toggleClassName('fruit'); * // -> Element - * + * * $('mutsu').hasClassName('fruit'); * // -> true - * + * * $('mutsu').toggleClassName('fruit', true); * // -> Element (keeps the "fruit" class name that was already there) **/ function toggleClassName(element, className, bool) { if (!(element = $(element))) return; - + if (Object.isUndefined(bool)) bool = !hasClassName(element, className); - + var method = Element[bool ? 'addClassName' : 'removeClassName']; return method(element, className); } - + var ATTRIBUTE_TRANSLATIONS = {}; - + // Test attributes. var classProp = 'className', forProp = 'for'; - + // Try "className" first (IE <8) DIV.setAttribute(classProp, 'x'); if (DIV.className !== 'x') { @@ -2576,7 +2583,7 @@ if (DIV.className === 'x') classProp = 'class'; } - + var LABEL = document.createElement('label'); LABEL.setAttribute(forProp, 'x'); if (LABEL.htmlFor !== 'x') { @@ -2585,30 +2592,30 @@ forProp = 'htmlFor'; } LABEL = null; - + function _getAttr(element, attribute) { return element.getAttribute(attribute); } - + function _getAttr2(element, attribute) { return element.getAttribute(attribute, 2); } - + function _getAttrNode(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ''; } - + function _getFlag(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; } - + // Test whether attributes like `onclick` have their values serialized. DIV.onclick = Prototype.emptyFunction; var onclickValue = DIV.getAttribute('onclick'); - + var _getEv; - + // IE <8 if (String(onclickValue).indexOf('{') > -1) { // intrinsic event attributes are serialized as `function { ... }` @@ -2620,7 +2627,7 @@ value = value.split('}')[0]; return value.strip(); }; - } + } // IE >=8 else if (onclickValue === '') { // only function body is serialized @@ -2630,7 +2637,7 @@ return value.strip(); }; } - + ATTRIBUTE_TRANSLATIONS.read = { names: { 'class': classProp, @@ -2638,7 +2645,7 @@ 'for': forProp, 'htmlFor': forProp }, - + values: { style: function(element) { return element.style.cssText.toLowerCase(); @@ -2648,7 +2655,7 @@ } } }; - + ATTRIBUTE_TRANSLATIONS.write = { names: { className: 'class', @@ -2656,31 +2663,37 @@ cellpadding: 'cellPadding', cellspacing: 'cellSpacing' }, - + values: { checked: function(element, value) { - element.checked = !!value; + value = !!value; + element.checked = value; + // Return the string that should be written out as its actual + // attribute. If we're unchecking, return `null` so that + // `writeAttribute` knows to remove the `checked` attribute + // altogether. + return value ? 'checked' : null; }, - + style: function(element, value) { element.style.cssText = value ? value : ''; } } }; - + ATTRIBUTE_TRANSLATIONS.has = { names: {} }; - + Object.extend(ATTRIBUTE_TRANSLATIONS.write.names, ATTRIBUTE_TRANSLATIONS.read.names); - + var CAMEL_CASED_ATTRIBUTE_NAMES = $w('colSpan rowSpan vAlign dateTime ' + 'accessKey tabIndex encType maxLength readOnly longDesc frameBorder'); - + for (var i = 0, attr; attr = CAMEL_CASED_ATTRIBUTE_NAMES[i]; i++) { ATTRIBUTE_TRANSLATIONS.write.names[attr.toLowerCase()] = attr; ATTRIBUTE_TRANSLATIONS.has.names[attr.toLowerCase()] = attr; } - + // The rest of the oddballs. Object.extend(ATTRIBUTE_TRANSLATIONS.read.values, { href: _getAttr2, @@ -2708,10 +2721,10 @@ onsubmit: _getEv, onreset: _getEv, onselect: _getEv, - onchange: _getEv + onchange: _getEv }); - - + + Object.extend(methods, { identify: identify, readAttribute: readAttribute, @@ -2722,56 +2735,56 @@ removeClassName: removeClassName, toggleClassName: toggleClassName }); - - + + // STYLES function normalizeStyleName(style) { if (style === 'float' || style === 'styleFloat') return 'cssFloat'; return style.camelize(); } - + function normalizeStyleName_IE(style) { if (style === 'float' || style === 'cssFloat') return 'styleFloat'; return style.camelize(); } - /** + /** * Element.setStyle(@element, styles) -> Element - * + * * Modifies `element`'s CSS style properties. Styles are passed as a hash of * property-value pairs in which the properties are specified in their * camelized form. - * + * * ##### Examples - * + * * $(element).setStyle({ * backgroundColor: '#900', * fontSize: '12px' * }); * // -> Element - * + * * ##### Notes - * + * * The method transparently deals with browser inconsistencies for `float` * (however, as `float` is a reserved keyword, you must either escape it or * use `cssFloat` instead) and `opacity` (which accepts values between `0` * -fully transparent- and `1` -fully opaque-). You can safely use either of * the following across all browsers: - * + * * $(element).setStyle({ * cssFloat: 'left', * opacity: 0.5 * }); * // -> Element - * + * * $(element).setStyle({ * 'float': 'left', // notice how float is surrounded by single quotes * opacity: 0.5 * }); * // -> Element - * + * * Not all CSS shorthand properties are supported. You may only use the CSS * properties described in the * [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). @@ -2779,7 +2792,7 @@ function setStyle(element, styles) { element = $(element); var elementStyle = element.style, match; - + if (Object.isString(styles)) { // Set the element's CSS text directly. elementStyle.cssText += ';' + styles; @@ -2789,7 +2802,7 @@ } return element; } - + for (var property in styles) { if (property === 'opacity') { Element.setOpacity(element, styles[property]); @@ -2804,10 +2817,10 @@ elementStyle[property] = value; } } - - return element; + + return element; } - + /** * Element.getStyle(@element, style) -> String | Number | null @@ -2823,30 +2836,30 @@ * (fully transparent) and `1` (fully opaque), position properties * (`left`, `top`, `right` and `bottom`) and when getting the dimensions * (`width` or `height`) of hidden elements. - * + * * If a value is present, it will be returned as a string — except * for `opacity`, which returns a number between `0` and `1` just as * [[Element.getOpacity]] does. - * + * * ##### Examples - * + * * $(element).getStyle('font-size'); * // equivalent: - * + * * $(element).getStyle('fontSize'); * // -> '12px' - * + * * ##### Notes - * + * * Not all CSS shorthand properties are supported. You may only use the CSS * properties described in the * [Document Object Model (DOM) Level 2 Style Specification](http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-ElementCSSInlineStyle). - * + * * Old versions of Internet Explorer return _literal_ values; other browsers * return _computed_ values. * * Consider the following HTML snippet: - * + * * language: html * + + +

        visible

        +

        hidden

        + + diff --git a/test/unit/tests/dom.test.js b/test/unit/tests/dom.test.js index 68767d27c..1ef310a33 100644 --- a/test/unit/tests/dom.test.js +++ b/test/unit/tests/dom.test.js @@ -338,11 +338,20 @@ suite('DOM', function () { assert.equal(element.up(), wrapper); }); - test('#visible', function () { + test('#visible', function (done) { assert.notEqual('none', $('test-visible').style.display); assert($('test-visible').visible()); assert.equal('none', $('test-hidden').style.display); assert(!$('test-hidden').visible()); + assert(!$('test-hidden-by-stylesheet').visible()); + var iframe = $('iframe'); + // Wait to make sure the IFRAME has loaded. + setTimeout(function () { + var paragraphs = iframe.contentWindow.document.querySelectorAll('p'); + assert(Element.visible(paragraphs[0])); + assert(!Element.visible(paragraphs[1])); + done(); + }, 500); }); test('#toggle', function () { diff --git a/test/unit/views/tests/dom.erb b/test/unit/views/tests/dom.erb index b0c7f9460..e7c694e9f 100644 --- a/test/unit/views/tests/dom.erb +++ b/test/unit/views/tests/dom.erb @@ -83,14 +83,26 @@ div.style-test { margin-left: 1px } body { height: 40000px; } + +#test-hidden-by-stylesheet { + display: none; +} + +#iframe { + width: 1px; + height: 1px; +}

        Scroll test

        + +
        visible
        +
        hidden
        visible
        visible
        From dee2f7d8611248abce81287e1be4156011953c90 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 9 Apr 2017 18:19:06 -0500 Subject: [PATCH 502/502] Remove test I inadvertently left in. --- test/unit/tests/ajax.test.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/unit/tests/ajax.test.js b/test/unit/tests/ajax.test.js index 55abb0867..de74074dd 100644 --- a/test/unit/tests/ajax.test.js +++ b/test/unit/tests/ajax.test.js @@ -541,19 +541,5 @@ suite("Ajax", function () { } })); }); - - test('no exception handler', function (done) { - new Ajax.Request('/inspect', extendDefault({ - onSuccess: function () { - try { - throw new Error("foo"); - assert(true); - } finally { - done(); - } - } - })); - }); - });