Compare commits

..

70 Commits

Author SHA1 Message Date
John Bintz a36b6d3e76 add unit test for getStyle where passed object does not have style attribute 2010-01-15 14:00:07 -05:00
John Bintz 2ede0b24e2 coding style change 2010-01-12 14:51:36 -05:00
John Bintz 1c21c2f1b6 remaining IE6 fixes for offset positioning where null objects are being returned 2010-01-12 14:46:52 -05:00
John Bintz 8a5b3209f4 add check to getStyle to ensure provided element has a style attribute 2010-01-06 19:43:10 -05:00
John Bintz 5992866e46 Revert "potential workaround for IE 5 issue where element can become something other than a DOM node"
This reverts commit ef78a973ef.
2010-01-06 19:41:33 -05:00
John Bintz ef78a973ef potential workaround for IE 6 issue where element can become something other than a DOM node 2010-01-06 16:52:58 -05:00
Tobie Langel 6839886699 doc: Update PDoc. Default to BlueCloth Markdown parser to avoid Maruku warning. 2009-12-31 19:34:35 +01:00
Juriy Zaytsev 475f1797e1 No need to check for `window.Node`, since `var Node` already declares it if missing. 2009-12-30 01:45:59 -05:00
Juriy Zaytsev e0a102b510 Merge branch 'master' of github.com:sstephenson/prototype 2009-12-30 01:41:27 -05:00
Juriy Zaytsev a260913a30 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). 2009-12-30 01:33:37 -05:00
Tobie Langel 6c1790ac4e doc: Fix Template doc to avoid issues with Sprockets. 2009-12-29 01:54:56 +01:00
Tobie Langel 4cc67f2e8e doc: Minor cleanup. 2009-12-29 01:53:16 +01:00
Tobie Langel 8a8af0a729 doc: Fix documentation for Template. 2009-12-24 07:32:32 +01:00
Tobie Langel 2e3e02d92c doc: Fix documentation errors in Ajax.Response. 2009-12-24 03:19:14 +01:00
Tobie Langel bccb541797 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-12-24 03:18:11 +01:00
Juriy Zaytsev 56fb5b84a1 No need to use expensive try/catch when check for element being orphaned suffices. 2009-12-23 01:54:19 -05:00
Tobie Langel d085d97a56 Revert back to PDoc's default templates. 2009-12-23 06:51:56 +01:00
Tobie Langel fb93b80b59 doc: using pygments for syntax highlighting. 2009-12-23 06:32:59 +01:00
Tobie Langel a7f05ee8b5 doc: Fix a typo in Prototype.Selector.find which prevented proper documentation parsing. Add a default value for the index argument. 2009-12-23 05:52:51 +01:00
Juriy Zaytsev 647d93bcc4 Use native `Array.isArray` when available; currently present in nightly webkit (up to 17x faster) and Opera 10.5 alpha (up to 5x faster). 2009-12-22 18:05:30 -05:00
Andrew Dupont d6ed7efe94 Tweak the PDoc templates. 2009-12-17 14:52:27 -06:00
Andrew Dupont 83b0c153d3 Add examples for String#gsub and String#sub.
(cherry picked from commit 591b25eb2604953754f08c40e9ef99791a6bb51b)
2009-12-04 18:29:40 -06:00
Andrew Dupont c028935279 Add note about mouseenter/leave support to docs.
(cherry picked from commit 1c4c2005341ef587dca7eacc8d347367e3eb7ef7)
2009-12-04 18:28:31 -06:00
Andrew Dupont 0dd600974c Add documentation for event key codes.
(cherry picked from commit 93bd2048e2c106ecc309292be358ef398df05984)
2009-12-04 18:27:49 -06:00
Andrew Dupont e7c9072872 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-12-04 18:25:23 -06:00
Sam Stephenson fa0dce2488 Revert Event#findElement changes from 6bb309a 2009-12-03 00:31:35 -06:00
Sam Stephenson 6bb309afb7 Selector API change: replace Prototype.Selector.filter with Prototype.Selector.find, which works like the old Selector.findElement. 2009-12-02 15:57:58 -06:00
Sam Stephenson 7770ab99dc Fix 'rake' without SELECTOR_ENGINE environment variable set 2009-12-02 15:14:18 -06:00
Tobie Langel a44a8db6ae Avoid automatically fetching the vendor/sizzle git submodule. 2009-12-02 21:41:52 +01:00
Sam Stephenson 797c231bfa Move ext/sizzle into src so it's available in the default Sprockets load path. 2009-12-02 14:18:01 -06:00
Tobie Langel 9d4711281d Fix typo in Rakefile. 2009-12-02 00:47:16 +01:00
Samuel Lebeau 74c5d45511 Fix custom selector engine load path resolution. 2009-11-30 12:45:13 +01:00
Tobie Langel 6b00f24963 Merge branch 'selector_agnostic'
Conflicts:
	src/dom/selector.js
2009-11-30 11:32:09 +01:00
Tobie Langel cdb41a170f Add sizzle to ext/. 2009-11-30 11:12:26 +01:00
Juriy Zaytsev b3fc07922c Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. 2009-11-13 15:49:22 -05:00
Juriy Zaytsev 18f2ac65c3 Optimize Element#immediateDescendants. 2009-11-13 14:28:27 -05:00
Juriy Zaytsev 82c9b9c783 Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. 2009-11-13 14:21:14 -05:00
Juriy Zaytsev 9ef3f8d2ed Eliminate runtime forking and long method lookup in `Element.hasAttribute`. 2009-11-13 12:27:10 -05:00
Juriy Zaytsev 829800834d Do not create translations object every time method is called. 2009-11-12 21:21:42 -05:00
Juriy Zaytsev c272e40042 Minor optimization in class module. 2009-11-12 21:17:37 -05:00
Juriy Zaytsev f40fd5a7d6 Remove redundant ternary. 2009-11-12 21:15:52 -05:00
Juriy Zaytsev 1f167f8754 `if` should be followed by space. 2009-11-11 17:43:25 -05:00
Juriy Zaytsev 01a229011a Avoid repeating declaration statements. 2009-11-11 17:32:19 -05:00
Tobie Langel 678774cbd6 Minor changes to the Sizzle adapter. 2009-10-26 02:00:55 +01:00
Tobie Langel 74ae0a5537 Renamed Prototype.Legacy to Prototype.LegacySelector by popular request. 2009-10-25 16:35:59 +01:00
Tobie Langel 70c5e98d44 Make the UpdaterHelper function on WebKit-based browsers. 2009-10-25 16:24:51 +01:00
Tobie Langel 24569d1d98 Deprecate the Selector API. 2009-10-25 16:13:11 +01:00
Tobie Langel fdf3424f78 Add unit tests. 2009-10-25 00:40:48 +02:00
Tobie Langel 107f812525 Modify PrototypeHelper.sprocketize to take a hash of options rather than separate arguments. 2009-10-24 22:17:37 +02:00
Tobie Langel 83826829a7 Add Prototype.Selector.engine which simply holds a reference to the actual selector engine used. 2009-10-24 16:12:38 +02:00
Tobie Langel 5f85799c3f Refactor NWMatcher adapter. 2009-10-23 19:29:03 +02:00
Tobie Langel 75aab03eba Repo and Rakefile refactoring. 2009-10-23 18:32:07 +02:00
Tobie Langel 7f5ce1e6c2 Clean-up NWMatcher proxy. 2009-10-23 13:17:53 +02:00
Tobie Langel f6f6955a71 Marked old Selector API as deprecated. 2009-10-23 12:58:10 +02:00
Tobie Langel b0159bdba7 Document Prototype.Selector API. 2009-10-23 12:53:03 +02:00
Tobie Langel ed27b225a5 Fix Element extension when using the NWMatcher selector engine. (jddalton) 2009-10-23 07:31:37 +02:00
Tobie Langel 15c323b9ac Include NWMatcher as a submodule. 2009-10-23 07:18:52 +02:00
Tobie Langel 17e8064d8a Add legacy Prototype selector engine. Acessible as Prototype.Legacy. Use SELECTOR_ENGINE=legacy to build. 2009-10-23 06:39:59 +02:00
Tobie Langel cba5468b09 Nitpicking. 2009-10-23 06:38:27 +02:00
Tobie Langel 7762e002cb 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. 2009-10-23 05:49:40 +02:00
Tobie Langel 3e19f959a2 Fix Selector#findElements. 2009-10-23 04:42:05 +02:00
Tobie Langel da3e1e361e Clean-up. 2009-10-23 04:10:30 +02:00
Tobie Langel 2d13d45dc8 Remove dependencies to Selector in favor of Prototype.Selector throughout the library. 2009-10-23 01:43:48 +02:00
Tobie Langel caf66395d5 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. 2009-10-23 01:17:53 +02:00
Andrew Dupont d3df9ba400 Automate installation of Git submodules. 2009-10-11 18:49:14 -05:00
Andrew Dupont c5372d81f3 Detect if Sizzle hasn't been loaded via submodule. 2009-10-10 19:07:37 -05:00
Sam Stephenson 4d042a9fa0 Match each element individually in Selector.findElement since Sizzle.matches does not preserve order 2009-10-10 13:24:04 -05:00
Sam Stephenson 79cf30aab1 Extend matched elements 2009-09-19 16:17:43 -05:00
Sam Stephenson 4dd878f237 Replace Prototype's Selector implementation with Sizzle 2009-09-19 14:42:59 -05:00
Sam Stephenson 9e4a7ce8e8 Don't call private Selector methods in Element#previous and Element#next 2009-09-19 14:40:01 -05:00
77 changed files with 2604 additions and 10863 deletions

7
.gitmodules vendored
View File

@ -11,3 +11,10 @@
path = vendor/sprockets
url = git://github.com/sstephenson/sprockets.git
[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

View File

@ -1,3 +1,15 @@
* 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)
* 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)
* Make `Event.stopObserving` return element in all cases. [#810 state:resolved] (Yaffle, Tobie Langel)
* String#startsWith, String#endsWith performance optimization (Yaffle, Tobie Langel, kangax)

114
Rakefile
View File

@ -13,31 +13,72 @@ 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
def self.has_git?
begin
`git --version`
return true
rescue Error => e
return false
end
end
def self.sprocketize(path, source, destination = nil, strip_comments = true)
def self.require_git
return if has_git?
puts "\nPrototype requires Git in order to load its dependencies."
puts "\nMake sure you've got Git installed and in your path."
puts "\nFor more information, visit:\n\n"
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, path),
:load_path => [SRC_DIR],
: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)
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'] || DEFAULT_SELECTOR_ENGINE,
:strip_comments => false
)
rm_rf DOC_DIR
PDoc::Runner.new(temp_path, {
:output => DOC_DIR,
:templates => File.join(TEMPLATES_DIR, "html"),
:index_page => 'README.markdown'
}).run
PDoc.run({
:source_files => [temp_path],
:destination => DOC_DIR,
:index_page => 'README.markdown',
:syntax_highlighter => :pygments,
:markdown_parser => :bluecloth
})
rm_rf temp_path
end
@ -58,17 +99,44 @@ module PrototypeHelper
require_submodule('CajaBuilder', 'caja_builder')
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"))
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"
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.
puts "\nLooks like it didn't work. Try it manually:\n\n"
puts " $ git submodule init"
puts " $ git submodule update vendor/#{path}"
false
end
def self.require_submodule(name, path)
begin
require path
rescue LoadError => e
# 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 -- ', '')
if missing_file == path
puts "\nIt looks like you're missing #{name}. Just run:\n\n"
puts " $ git submodule init"
puts " $ git submodule update vendor/#{path}"
puts "\nand you should be all set.\n\n"
# Missing a git submodule.
retry if get_submodule(name, path)
else
# Missing a gem.
puts "\nIt looks like #{name} is missing the '#{missing_file}' gem. Just run:\n\n"
puts " $ gem install #{missing_file}"
puts "\nand you should be all set.\n\n"
@ -78,15 +146,15 @@ module PrototypeHelper
end
end
%w[sprockets pdoc unittest_js caja_builder].each do |name|
$:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib')
end
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'] || PrototypeHelper::DEFAULT_SELECTOR_ENGINE
)
end
namespace :doc do
@ -104,7 +172,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|

View File

@ -81,7 +81,12 @@
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() {
@ -280,6 +285,38 @@
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 });

View File

@ -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([
@ -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'
}
]);

View File

@ -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 ''; }
}
},

View File

@ -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();
@ -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

View File

@ -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 "#<Selector: " + this.expression + ">";
}
});
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);
}
});
})();

View File

@ -19,9 +19,15 @@
*
**/
/** section: DOM
* Prototype
*
* The Prototype namespace.
*
**/
//= require "dom/dom"
//= require "dom/layout"
//= require <selector_engine>
//= require "dom/selector"
//= require "dom/form"
//= require "dom/event"

View File

@ -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.
*
@ -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
@ -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");
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;
(function(global) {
var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
try {
var el = document.createElement('<input name="x">');
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 = {
/**
@ -443,8 +440,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 + '>';
@ -458,12 +456,18 @@ Element.Methods = {
* won't do!) of `element` that points to a single DOM node (e.g.,
* `nextSibling` or `parentNode`).
**/
recursivelyCollect: function(element, property) {
recursivelyCollect: function(element, property, maximumLength) {
element = $(element);
maximumLength = maximumLength || -1;
var elements = [];
while (element = element[property])
while (element = element[property]) {
if (element.nodeType == 1)
elements.push(Element.extend(element));
if (elements.length == maximumLength)
break;
}
return elements;
},
@ -535,10 +539,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;
},
/**
@ -547,7 +555,7 @@ Element.Methods = {
* Collects all of `element`'s previous siblings and returns them as an
* array of elements.
**/
previousSiblings: function(element) {
previousSiblings: function(element, maximumLength) {
return Element.recursivelyCollect(element, 'previousSibling');
},
@ -579,9 +587,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);
},
/**
@ -599,7 +608,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.find(ancestors, expression, index);
},
/**
@ -631,10 +640,14 @@ Element.Methods = {
**/
previous: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
var previousSiblings = Element.previousSiblings(element);
return Object.isNumber(expression) ? previousSiblings[expression] :
Selector.findElement(previousSiblings, expression, index);
if (Object.isNumber(expression)) index = expression, expression = false;
if (!Object.isNumber(index)) index = 0;
if (expression) {
return Prototype.Selector.find(element.previousSiblings(), expression, index);
} else {
return element.recursivelyCollect("previousSibling", index + 1)[index];
}
},
/**
@ -649,23 +662,29 @@ Element.Methods = {
**/
next: function(element, expression, index) {
element = $(element);
if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
var nextSiblings = Element.nextSiblings(element);
return Object.isNumber(expression) ? nextSiblings[expression] :
Selector.findElement(nextSiblings, expression, index);
if (Object.isNumber(expression)) index = expression, expression = false;
if (!Object.isNumber(index)) index = 0;
if (expression) {
return Prototype.Selector.find(element.nextSiblings(), expression, index);
} else {
var maximumLength = Object.isNumber(index) ? index + 1 : 1;
return element.recursivelyCollect("nextSibling", index + 1)[index];
}
},
/**
* 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);
},
/**
@ -699,8 +718,9 @@ Element.Methods = {
* // -> [li#lon, li#tok]
**/
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);
},
/**
@ -984,6 +1004,9 @@ Element.Methods = {
**/
getStyle: function(element, style) {
element = $(element);
if (!element.style)
return null;
style = style == 'float' ? 'cssFloat' : style.camelize();
var value = element.style[style];
if (!value || value == 'auto') {
@ -1041,6 +1064,35 @@ 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
@ -1138,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);
},
@ -1180,11 +1234,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);
@ -1212,8 +1266,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';
@ -1281,9 +1335,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;
@ -1374,12 +1429,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') {
@ -1506,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' });
@ -1521,15 +1574,15 @@ 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
// accurate offsetTop and offsetLeft values for position: fixed.
var offsetParent = element.getOffsetParent();
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
offsetParent.setStyle({ zoom: 1 });
if (offsetParent.getStyle)
if (offsetParent && offsetParent.getStyle('position') === 'fixed')
offsetParent.setStyle({ zoom: 1 });
element.setStyle({ position: 'relative' });
var value = proceed(element);
element.setStyle({ position: position });
@ -1538,16 +1591,11 @@ 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);
if (!element.style)
return null;
style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
var value = element.style[style];
if (!value && element.currentStyle) value = element.currentStyle[style];
@ -1589,10 +1637,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');
@ -1637,10 +1684,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) {
@ -1774,7 +1820,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(' ');
@ -1817,8 +1863,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) });
@ -1840,11 +1886,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);
};
@ -1933,8 +1985,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];
@ -2008,10 +2060,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
@ -2199,8 +2255,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;
}
@ -2228,8 +2285,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 &mdash; the rectangle that represents the portion of a web

View File

@ -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,
@ -149,10 +152,14 @@
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;
}
}
/**
* Event.pointer(@event) -> Object
*
@ -379,12 +386,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);
};
}
@ -408,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
@ -613,6 +621,7 @@
* ...and then to remove:
*
* $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right
*
**/
function stopObserving(element, eventName, handler) {
element = $(element);
@ -736,7 +745,7 @@
});
/** section: DOM
* document
* document
*
* Prototype extends the built-in `document` object with several convenience
* methods related to events.

View File

@ -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, {

View File

@ -1,931 +0,0 @@
(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 (/\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;
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 0;
}
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;
}
var hasLayout = Prototype.K;
if ('currentStyle' in document.documentElement) {
hasLayout = function(element) {
if (!element.currentStyle.hasLayout) {
element.style.zoom = 1;
}
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;
}
/**
* class Element.Layout < Hash
*
* A set of key/value pairs representing measurements of various
* dimensions of an element.
*
* <h4>Overview</h4>
*
* 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.
*
* <h4>Usage</h4>
*
* 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)
*
* <h4>Caching</h4>
*
* 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.
*
* <h4>Hidden elements<h4>
*
* 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);
// 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);
},
// 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));
},
/**
* 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);
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');
element.setStyle({
position: 'absolute',
visibility: 'hidden',
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.parentNode, pLayout = $(parent).getLayout();
newWidth = pLayout.get('width') -
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' });
// 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;
},
/**
* 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, {
// 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'),
/**
* 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: {
'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) {
var offset = element.positionedOffset();
return offset.top;
},
'bottom': function(element) {
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) {
var offset = element.positionedOffset();
return offset.left;
},
'right': function(element) {
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) {
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 Object.isNumber(element.clientTop) ? element.clientTop :
getPixelValue(element, 'borderTopWidth');
},
'border-bottom': function(element) {
return Object.isNumber(element.clientBottom) ? element.clientBottom :
getPixelValue(element, 'borderBottomWidth');
},
'border-left': function(element) {
return Object.isNumber(element.clientLeft) ? element.clientLeft :
getPixelValue(element, 'borderLeftWidth');
},
'border-right': function(element) {
return Object.isNumber(element.clientRight) ? 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');
}
}
});
// 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
*
* 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 "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
},
/**
* Element.Offset#toString() -> String
**/
toString: function() {
return "[#{left}, #{top}]".interpolate(this);
},
/**
* Element.Offset#toArray() -> 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.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
*
* 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) {
// 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 (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);
}
/**
* 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, docBody = document.body;
var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
// Safari fix
if (element.offsetParent == docBody &&
Element.getStyle(element, 'position') == 'absolute') break;
} while (element = element.offsetParent);
element = forElement;
do {
// 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;
}
} while (element = element.parentNode);
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,
absolutize: absolutize,
relativize: relativize
});
function isBody(element) {
return element.nodeName.toUpperCase() === '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);
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);
},
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();
// 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);
}
});
}
})();

View File

@ -1,867 +1,66 @@
/* Portions of the Selector 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. */
/** section: DOM
* class Selector
*
* A class that queries the document for elements that match a given CSS
* selector.
**/
var Selector = Class.create({
/**
* new Selector(expression)
* - expression (String): A CSS selector.
*
* Creates a `Selector` with the given CSS selector.
**/
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 = '<ul><li></li></ul><div><ul><li></li></ul></div>';
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 (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
if (!Selector._div) Selector._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 {
Selector._div.querySelector(this.expression);
} catch(e) {
return false;
}
return true;
},
compileMatcher: function() {
var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
c = Selector.criteria, le, p, m, len = ps.length, name;
if (Selector._cache[e]) {
this.matcher = Selector._cache[e];
return;
}
this.matcher = ["this.matcher = function(root) {",
"var r = root, h = Selector.handlers, c = false, n;"];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
p = ps[i].re;
name = ps[i].name;
if (m = e.match(p)) {
this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
new Template(c[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.matcher.push("return h.unique(n);\n}");
eval(this.matcher.join('\n'));
Selector._cache[this.expression] = this.matcher;
},
compileXPathMatcher: function() {
var e = this.expression, ps = Selector.patterns,
x = Selector.xpath, le, m, len = ps.length, name;
if (Selector._cache[e]) {
this.xpath = Selector._cache[e]; return;
}
this.matcher = ['.//*'];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
name = ps[i].name;
if (m = e.match(ps[i].re)) {
this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
new Template(x[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.xpath = this.matcher.join('');
Selector._cache[this.expression] = this.xpath;
},
/**
* 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(root) {
root = root || document;
var e = this.expression, results;
switch (this.mode) {
case 'selectorsAPI':
// querySelectorAll queries document-wide, then filters to descendants
// of the context element. That's not what we want.
// Add an explicit context to the selector if necessary.
if (root !== document) {
var oldId = root.id, id = $(root).identify();
// Escape special characters in the ID.
id = id.replace(/([\.:])/g, "\\$1");
e = "#" + id + " " + e;
}
results = $A(root.querySelectorAll(e)).map(Element.extend);
root.id = oldId;
return results;
case 'xpath':
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
},
/**
* Selector#match(element) -> Boolean
*
* Tests whether a `element` matches the instance's CSS selector.
**/
match: function(element) {
this.tokens = [];
var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
var le, p, m, len = ps.length, name;
while (e && le !== e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
p = ps[i].re;
name = ps[i].name;
if (m = e.match(p)) {
// use the Selector.assertions methods unless the selector
// is too complex.
if (as[name]) {
this.tokens.push([name, Object.clone(m)]);
e = e.replace(m[0], '');
} else {
// reluctantly do a document-wide search
// and look for a match in the array
return this.findElements(document).include(element);
}
}
}
}
var match = true, name, matches;
for (var i = 0, token; token = this.tokens[i]; i++) {
name = token[0], matches = token[1];
if (!Selector.assertions[name](element, matches)) {
match = false; break;
}
}
return match;
},
toString: function() {
return this.expression;
},
inspect: function() {
return "#<Selector:" + this.expression.inspect() + ">";
}
});
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.
Selector.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(Selector, {
_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(Selector.xpath.operators[m[2]]).evaluate(m);
},
pseudo: function(m) {
var h = Selector.xpath.pseudos[m[1]];
if (!h) return '';
if (Object.isFunction(h)) return h(m);
return new Template(Selector.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 = Selector.patterns,
x = Selector.xpath, le, v, len = p.length, name;
var exclusion = [];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
name = p[i].name;
if (m = e.match(p[i].re)) {
v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
exclusion.push("(" + v.substring(1, v.length - 1) + ")");
e = e.replace(m[0], '');
break;
}
}
}
return "[not(" + exclusion.join(" and ") + ")]";
},
'nth-child': function(m) {
return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
},
'nth-last-child': function(m) {
return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
},
'nth-of-type': function(m) {
return Selector.xpath.pseudos.nth("position() ", m);
},
'nth-last-of-type': function(m) {
return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
},
'first-of-type': function(m) {
m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
},
'last-of-type': function(m) {
m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
},
'only-of-type': function(m) {
var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
},
nth: function(fragment, m) {
var mm, formula = m[6], predicate;
if (formula == 'even') formula = '2n+0';
if (formula == 'odd') formula = '2n+1';
if (mm = formula.match(/^(\d+)$/)) // digit only
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;
predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
"((#{fragment} - #{b}) div #{a} >= 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 Selector.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 && Selector.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 Selector.handlers.unmark(results);
},
// COMBINATOR FUNCTIONS
descendant: function(nodes) {
var h = Selector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
h.concat(results, node.getElementsByTagName('*'));
return results;
},
child: function(nodes) {
var h = Selector.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 = Selector.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 = Selector.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 = Selector.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 (Selector.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 Selector.handlers.byClassName(nodes, root, className);
},
byClassName: function(nodes, root, className) {
if (!nodes) nodes = Selector.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 = Selector.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 Selector.pseudos[name](nodes, value, root);
}
},
pseudos: {
'first-child': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
if (Selector.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 (Selector.handlers.nextElementSibling(node)) continue;
results.push(node);
}
return results;
},
'only-child': function(nodes, value, root) {
var h = Selector.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 Selector.pseudos.nth(nodes, formula, root);
},
'nth-last-child': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, true);
},
'nth-of-type': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, false, true);
},
'nth-last-of-type': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, formula, root, true, true);
},
'first-of-type': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, "1", root, false, true);
},
'last-of-type': function(nodes, formula, root) {
return Selector.pseudos.nth(nodes, "1", root, true, true);
},
'only-of-type': function(nodes, formula, root) {
var p = Selector.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 = Selector.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 = 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);
}
}
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 = Selector.handlers, selectorType, m;
var 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);
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() + '-'); }
},
/**
* Selector.split(expression) -> [String...]
*
* Takes a string of CSS selectors separated by commas; returns an array
* of individual selectors.
*
* Safer than doing a naive `Array#split`, since selectors can have commas
* in other places.
**/
split: function(expression) {
var expressions = [];
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
expressions.push(m[1].strip());
});
return expressions;
},
/**
* 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: function(elements, expression) {
var matches = $$(expression), h = Selector.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;
},
/**
* 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) {
if (Object.isNumber(expression)) {
index = expression; expression = false;
}
return Selector.matchElements(elements, expression || '*')[index || 0];
},
/**
* Selector.findChildElements(element, expressions) -> [Element...]
*
* Searches beneath `element` for any elements that match the selector
* (or selectors) specified in `expressions`.
**/
findChildElements: function(element, expressions) {
expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
h.concat(results, selector.findElements(element));
}
return (l > 1) ? h.unique(results) : results;
}
});
if (Prototype.Browser.IE) {
Object.extend(Selector.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;
}
});
}
/** related to: Selector
/** section: DOM, related to: Prototype.Selector
* $$(expression...) -> [Element...]
*
* Returns all elements in the document that match the provided CSS selectors.
**/
function $$() {
return Selector.findChildElements(document, $A(arguments));
window.$$ = function() {
var expression = $A(arguments).join(', ');
return Prototype.Selector.select(expression, document);
};
/**
* 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.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;
var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
for (i = 0; i < length; i++) {
if (match(elements[i], expression) && index == matchIndex++) {
return Element.extend(elements[i]);
}
}
}
}

View File

@ -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.
@ -59,7 +70,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)
@ -135,12 +146,13 @@ 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.
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)
@ -150,7 +162,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); };

View File

@ -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

View File

@ -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.
*
* <h5>Example</h5>
*
* ""hello".gsub(/([aeiou])/, '<#{1}>');
* // => "h<e>ll<o>"
**/
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.
*
* <h5>Example</h5>
*
* "20091201".sub(/^(\d{4})(\d{2})(\d{2})$/, "#{1}-#{2}-#{3}");
* // => "2009-12-01"
**/
function sub(pattern, replacement, count) {
replacement = prepareReplacement(replacement);
@ -167,8 +177,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 +261,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) {
@ -482,7 +493,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,

View File

@ -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 `<&#38;= %>`
* together a template that uses a syntax similar to the now ubiquitous `{{ }}`
* constructs:
*
* // matches symbols like '<&#38;= field %>'
* var syntax = /(^|.|\r|\n)(\<%=\s*(\w+)\s*%\>)/;
* // matches symbols like '{{ field }}'
* var syntax = /(^|.|\r|\n)(\{{\s*(\w+)\s*}})/;
*
* var t = new Template(
* '<div>Name: <b><&#38;= name %></b>, Age: <b><&#38;=age%></b></div>',
* '<div>Name: <b>{{ name }}</b>, Age: <b>{{ age }}</b></div>',
* syntax);
* t.evaluate( {name: 'John Smith', age: 26} );
* // -> <div>Name: <b>John Smith</b>, Age: <b>26</b></div>
@ -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;

6
src/prototype.js vendored
View File

@ -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;

29
src/selector_engine.js Normal file
View File

@ -0,0 +1,29 @@
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 select(selector, scope) {
return extend(engine(selector, scope || document));
}
function match(element, selector) {
return engine.matches(selector, [element]).length == 1;
}
return {
engine: engine,
select: select,
match: match
};
})(Sizzle);
// Restore globals.
window.Sizzle = Prototype._original_property;
delete Prototype._original_property;

1015
src/sizzle.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

View File

@ -1,525 +0,0 @@
//= require <prototype>
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 "#<Cookie #{name}:#{value}>".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;
function poll() {
var hash = window.location.hash;
if (hash && hash !== PREVIOUS_HASH) {
document.fire('hash:changed', {
previous: PREVIOUS_HASH, current: hash
});
}
PREVIOUS_HASH = hash;
window.setTimeout(arguments.callee, 100);
}
Event.observe(window, 'load', poll);
})();
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'));
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) {
$('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';
},
// 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.
PDoc.Sidebar.Filterer = Class.create({
initialize: function(element, options) {
this.element = $(element);
this.options = Object.extend(
Object.clone(PDoc.Sidebar.Filterer.DEFAULT_OPTIONS),
options || {}
);
// The browser's "helpful" auto-complete gets in the way.
this.element.writeAttribute("autocomplete", "off");
this.element.setValue('');
// Hitting "enter" should do nothing.
this.element.up('form').observe("submit", Event.stop);
this.menu = this.options.menu;
this.links = this.menu.select('a');
this.resultsElement = this.options.resultsElement;
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();
},
addObservers: function() {
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) {
// Clear the text box on ESC.
if (event.keyCode && event.keyCode === Event.KEY_ESC) {
this.element.setValue('');
}
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.emptyResults();
this.hideResults();
return;
}
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 (!PDoc.Sidebar.Filterer.INTERCEPT_KEYS.include(event.keyCode))
return;
// Also ignore if any modifier keys are present.
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
return;
event.stop();
if (event.keyCode === Event.KEY_RETURN) {
// 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;
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);
}
}
},
keyup: function(event) {
if (this._scrollTimer) {
window.clearTimeout(this._scrollTimer);
}
},
moveHighlight: function(direction) {
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';
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');
this.highlighted = adjacent;
}
var h = this.highlighted, r = this.resultsElement;
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 this.highlighted;
},
scrollList: function(direction) {
this.moveHighlight(direction);
this._scrollTimer = window.setTimeout(
this.scrollList.bind(this, direction), 100);
}
});
Object.extend(PDoc.Sidebar.Filterer, {
INTERCEPT_KEYS: [Event.KEY_UP, Event.KEY_DOWN, Event.KEY_RETURN],
DEFAULT_OPTIONS: {
interval: 0.1,
resultsElement: '.search-results'
}
});
Form.GhostedField = Class.create({
initialize: function(element, title, options) {
options = options || {};
this.element = $(element);
this.title = title;
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();
},
wrappedGetValue: function($proceed) {
var value = $proceed();
return value === this.title ? "" : value;
},
addObservers: function() {
this.element.observe('focus', this.onFocus.bind(this));
this.element.observe('blur', this.onBlur.bind(this));
var form = this.element.up('form');
if (form) {
form.observe('submit', this.onSubmit.bind(this));
}
// Firefox's bfcache means that form fields need to be re-initialized
// when you hit the "back" button to return to the page.
if (Prototype.Browser.Gecko) {
window.addEventListener('pageshow', this.onBlur.bind(this), false);
}
},
onFocus: function() {
if (this.isGhosted) {
this.element.setValue('');
this.setGhosted(false);
}
},
onBlur: function() {
var value = this.element.getValue();
if (value.blank() || value == this.title) {
this.setGhosted(true);
} else {
this.setGhosted(false);
}
},
setGhosted: function(isGhosted) {
this.isGhosted = isGhosted;
this.element[isGhosted ? 'addClassName' : 'removeClassName']('ghosted');
if (isGhosted) {
this.element.setValue(this.title);
}
},
// Hook into the enclosing form's `onsubmit` event so that we clear any
// ghosted text before the form is sent.
onSubmit: function() {
if (this.isGhosted) {
this.element.setValue('');
}
}
});
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());
});

View File

@ -1,963 +0,0 @@
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = { }
Autocompleter.Base = Class.create({
baseInitialize: function(element, update, options) {
element = $(element)
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
this.oldElementValue = this.element.value;
if(this.setOptions)
this.setOptions(options);
else
this.options = options || { };
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
// Force carriage returns as token delimiters anyway
if (!this.options.tokens.include('\n'))
this.options.tokens.push('\n');
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(Prototype.Browser.IE) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
this.iefix = $(this.update.id+'_iefix');
}
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
},
fixIEOverlapping: function() {
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--
else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++
else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = $(selectedElement).select('.' + this.options.select) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var bounds = this.getTokenBounds();
if (bounds[0] != -1) {
var newValue = this.element.value.substr(0, bounds[0]);
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
} else {
this.element.value = value;
}
this.oldElementValue = this.element.value;
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount =
this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
this.tokenBounds = null;
if(this.getToken().length>=this.options.minChars) {
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
this.oldElementValue = this.element.value;
},
getToken: function() {
var bounds = this.getTokenBounds();
return this.element.value.substring(bounds[0], bounds[1]).strip();
},
getTokenBounds: function() {
if (null != this.tokenBounds) return this.tokenBounds;
var value = this.element.value;
if (value.strip().empty()) return [-1, 0];
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
var offset = (diff == this.oldElementValue.length ? 1 : 0);
var prevTokenPos = -1, nextTokenPos = value.length;
var tp;
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
if (tp > prevTokenPos) prevTokenPos = tp;
tp = value.indexOf(this.options.tokens[index], diff + offset);
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
}
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
}
});
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
var boundary = Math.min(newS.length, oldS.length);
for (var index = 0; index < boundary; ++index)
if (newS[index] != oldS[index])
return index;
return boundary;
};
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
initialize: function(element, update, url, options) {
this.baseInitialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this);
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
this.startIndicator();
var entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create(Autocompleter.Base, {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || { });
}
});
// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
setTimeout(function() {
Field.activate(field);
}, 1);
}
Ajax.InPlaceEditor = Class.create({
initialize: function(element, url, options) {
this.url = url;
this.element = element = $(element);
this.prepareOptions();
this._controls = { };
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
Object.extend(this.options, options || { });
if (!this.options.formId && this.element.id) {
this.options.formId = this.element.id + '-inplaceeditor';
if ($(this.options.formId))
this.options.formId = '';
}
if (this.options.externalControl)
this.options.externalControl = $(this.options.externalControl);
if (!this.options.externalControl)
this.options.externalControlOnly = false;
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
this.element.title = this.options.clickToEditText;
this._boundCancelHandler = this.handleFormCancellation.bind(this);
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
this._boundWrapperHandler = this.wrapUp.bind(this);
this.registerListeners();
},
checkForEscapeOrReturn: function(e) {
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (Event.KEY_ESC == e.keyCode)
this.handleFormCancellation(e);
else if (Event.KEY_RETURN == e.keyCode)
this.handleFormSubmission(e);
},
createControl: function(mode, handler, extraClasses) {
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if ('button' == control) {
var btn = document.createElement('input');
btn.type = 'submit';
btn.value = text;
btn.className = 'editor_' + mode + '_button';
if ('cancel' == mode)
btn.onclick = this._boundCancelHandler;
this._form.appendChild(btn);
this._controls[mode] = btn;
} else if ('link' == control) {
var link = document.createElement('a');
link.href = '#';
link.appendChild(document.createTextNode(text));
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
link.className = 'editor_' + mode + '_link';
if (extraClasses)
link.className += ' ' + extraClasses;
this._form.appendChild(link);
this._controls[mode] = link;
}
},
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
var fld;
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
fld = document.createElement('input');
fld.type = 'text';
var size = this.options.size || this.options.cols || 0;
if (0 < size) fld.size = size;
} else {
fld = document.createElement('textarea');
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
fld.cols = this.options.cols || 40;
}
fld.name = this.options.paramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.submitOnBlur)
fld.onblur = this._boundSubmitHandler;
this._controls.editor = fld;
if (this.options.loadTextURL)
this.loadExternalText();
this._form.appendChild(this._controls.editor);
},
createForm: function() {
var ipe = this;
function addText(mode, condition) {
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appendChild(document.createTextNode(text));
};
this._form = $(document.createElement('form'));
this._form.id = this.options.formId;
this._form.addClassName(this.options.formClassName);
this._form.onsubmit = this._boundSubmitHandler;
this.createEditField();
if ('textarea' == this._controls.editor.tagName.toLowerCase())
this._form.appendChild(document.createElement('br'));
if (this.options.onFormCustomization)
this.options.onFormCustomization(this, this._form);
addText('Before', this.options.okControl || this.options.cancelControl);
this.createControl('ok', this._boundSubmitHandler);
addText('Between', this.options.okControl && this.options.cancelControl);
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
addText('After', this.options.okControl || this.options.cancelControl);
},
destroy: function() {
if (this._oldInnerHTML)
this.element.innerHTML = this._oldInnerHTML;
this.leaveEditMode();
this.unregisterListeners();
},
enterEditMode: function(e) {
if (this._saving || this._editing) return;
this._editing = true;
this.triggerCallback('onEnterEditMode');
if (this.options.externalControl)
this.options.externalControl.hide();
this.element.hide();
this.createForm();
this.element.parentNode.insertBefore(this._form, this.element);
if (!this.options.loadTextURL)
this.postProcessEditField();
if (e) Event.stop(e);
},
enterHover: function(e) {
if (this.options.hoverClassName)
this.element.addClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onEnterHover');
},
getText: function() {
return this.element.innerHTML;
},
handleAJAXFailure: function(transport) {
this.triggerCallback('onFailure', transport);
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
this._oldInnerHTML = null;
}
},
handleFormCancellation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
},
handleFormSubmission: function(e) {
var form = this._form;
var value = $F(this._controls.editor);
this.prepareSubmission();
var params = this.options.callback(form, value) || '';
if (Object.isString(params))
params = params.toQueryParams();
params.editorId = this.element.id;
if (this.options.htmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.url, options);
}
if (e) Event.stop(e);
},
leaveEditMode: function() {
this.element.removeClassName(this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
if (this.options.externalControl)
this.options.externalControl.show();
this._saving = false;
this._editing = false;
this._oldInnerHTML = null;
this.triggerCallback('onLeaveEditMode');
},
leaveHover: function(e) {
if (this.options.hoverClassName)
this.element.removeClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onLeaveHover');
},
loadExternalText: function() {
this._form.addClassName(this.options.loadingClassName);
this._controls.editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._form.removeClassName(this.options.loadingClassName);
var text = transport.responseText;
if (this.options.stripLoadedTextTags)
text = text.stripTags();
this._controls.editor.value = text;
this._controls.editor.disabled = false;
this.postProcessEditField();
}.bind(this),
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.options.loadTextURL, options);
},
postProcessEditField: function() {
var fpc = this.options.fieldPostCreation;
if (fpc)
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
},
prepareOptions: function() {
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
Object.extend(this.options, defs);
}.bind(this));
},
prepareSubmission: function() {
this._saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
registerListeners: function() {
this._listeners = { };
var listener;
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options.externalControlOnly)
this.element.observe(pair.key, listener);
if (this.options.externalControl)
this.options.externalControl.observe(pair.key, listener);
}.bind(this));
},
removeForm: function() {
if (!this._form) return;
this._form.remove();
this._form = null;
this._controls = { };
},
showSaving: function() {
this._oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
this.element.addClassName(this.options.savingClassName);
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
},
triggerCallback: function(cbName, arg) {
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListeners: function() {
$H(this._listeners).each(function(pair) {
if (!this.options.externalControlOnly)
this.element.stopObserving(pair.key, pair.value);
if (this.options.externalControl)
this.options.externalControl.stopObserving(pair.key, pair.value);
}.bind(this));
},
wrapUp: function(transport) {
this.leaveEditMode();
// Can't use triggerCallback due to backward compatibility: requires
// binding + direct element
this._boundComplete(transport, this.element);
}
});
Object.extend(Ajax.InPlaceEditor.prototype, {
dispose: Ajax.InPlaceEditor.prototype.destroy
});
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
initialize: function($super, element, url, options) {
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
$super(element, url, options);
},
createEditField: function() {
var list = document.createElement('select');
list.name = this.options.paramName;
list.size = 1;
this._controls.editor = list;
this._collection = this.options.collection || [];
if (this.options.loadCollectionURL)
this.loadCollection();
else
this.checkForExternalText();
this._form.appendChild(this._controls.editor);
},
loadCollection: function() {
this._form.addClassName(this.options.loadingClassName);
this.showLoadingText(this.options.loadingCollectionText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
var js = transport.responseText.strip();
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
throw 'Server returned an invalid collection representation.';
this._collection = eval(js);
this.checkForExternalText();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadCollectionURL, options);
},
showLoadingText: function(text) {
this._controls.editor.disabled = true;
var tempOption = this._controls.editor.firstChild;
if (!tempOption) {
tempOption = document.createElement('option');
tempOption.value = '';
this._controls.editor.appendChild(tempOption);
tempOption.selected = true;
}
tempOption.update((text || '').stripScripts().stripTags());
},
checkForExternalText: function() {
this._text = this.getText();
if (this.options.loadTextURL)
this.loadExternalText();
else
this.buildOptionList();
},
loadExternalText: function() {
this.showLoadingText(this.options.loadingText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._text = transport.responseText.strip();
this.buildOptionList();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadTextURL, options);
},
buildOptionList: function() {
this._form.removeClassName(this.options.loadingClassName);
this._collection = this._collection.map(function(entry) {
return 2 === entry.length ? entry : [entry, entry].flatten();
});
var marker = ('value' in this.options) ? this.options.value : this._text;
var textFound = this._collection.any(function(entry) {
return entry[0] == marker;
}.bind(this));
this._controls.editor.update('');
var option;
this._collection.each(function(entry, index) {
option = document.createElement('option');
option.value = entry[0];
option.selected = textFound ? entry[0] == marker : 0 == index;
option.appendChild(document.createTextNode(entry[1]));
this._controls.editor.appendChild(option);
}.bind(this));
this._controls.editor.disabled = false;
Field.scrollFreeActivate(this._controls.editor);
}
});
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only exists for a while, in order to let ****
//**** users adapt to the new API. Read up on the new ****
//**** API and convert your code to it ASAP! ****
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
if (!options) return;
function fallback(name, expr) {
if (name in options || expr === undefined) return;
options[name] = expr;
};
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
options.cancelLink == options.cancelButton == false ? false : undefined)));
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
options.okLink == options.okButton == false ? false : undefined)));
fallback('highlightColor', options.highlightcolor);
fallback('highlightEndColor', options.highlightendcolor);
};
Object.extend(Ajax.InPlaceEditor, {
DefaultOptions: {
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button'|false
cancelText: 'cancel',
clickToEditText: 'Click to edit',
externalControl: null, // id|elt
externalControlOnly: false,
fieldPostCreation: 'activate', // 'activate'|'focus'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndColor: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassName: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button'|false
okText: 'ok',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName: 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedTextTags: false,
submitOnBlur: false,
textAfterControls: '',
textBeforeControls: '',
textBetweenControls: ''
},
DefaultCallbacks: {
callback: function(form) {
return Form.serialize(form);
},
onComplete: function(transport, element) {
// For backward compatibility, this one is bound to the IPE, and passes
// the element directly. It was too often customized, so we don't break it.
new Effect.Highlight(element, {
startcolor: this.options.highlightColor, keepBackgroundImage: true });
},
onEnterEditMode: null,
onEnterHover: function(ipe) {
ipe.element.style.backgroundColor = ipe.options.highlightColor;
if (ipe._effect)
ipe._effect.cancel();
},
onFailure: function(transport, ipe) {
alert('Error communication with the server: ' + transport.responseText.stripTags());
},
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
onLeaveEditMode: null,
onLeaveHover: function(ipe) {
ipe._effect = new Effect.Highlight(ipe.element, {
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
restorecolor: ipe._originalBackground, keepBackgroundImage: true
});
}
},
Listeners: {
click: 'enterEditMode',
keydown: 'checkForEscapeOrReturn',
mouseover: 'enterHover',
mouseout: 'leaveHover'
}
});
Ajax.InPlaceCollectionEditor.DefaultOptions = {
loadingCollectionText: 'Loading options...'
};
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create({
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,694 +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;
}
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: "Panic Sans", "Bitstream Vera Sans Mono", Monaco, Consolas, Andale Mono, monospace;
font-size: 12px;
}
#page a.img,
#page a.img:link,
#page a.img:visited {
border: 0;
}
.content {
padding-left: 120px;
margin: 0 auto;
}
#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;
}
ul.breadcrumbs li {
float: left;
list-style-type: none;
margin-right: 10px;
margin-left: 0;
}
/* PAGE CONTENT */
.page-content {
width: 715px;
margin: 30px 0 0;
}
.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;
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;
}
.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 {
}
.page-content h1 {
font-size: 27px;
color: #000;
margin: 1.0em 0 0.6em;
}
.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;
}
.section-content {
width: 595px;
float: left;
}
/* API STYLES */
.page-title .type {
display: block;
text-transform: uppercase;
font-size: 14px;
color: #aaa;
}
ul.section-list {
list-style-type: none;
margin-top: 0;
}
ul.section-list li {
list-style-type: none;
margin: 0 0 15px;
}
ul.section-list li h4 {
margin-top: 0;
}
ul.method-list {
margin-top: 0;
}
ul.method-list li {
float: left;
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;
}
li.method-description {
margin: 0 0 2.0em;
list-style-type: none;
}
.method-description h4 {
margin: 0 0 0.6em;
line-height: 90%;
}
.method-description p {
margin: 0.8em 0;
}
.method-description .method-permalink a {
color: #aaa;
text-decoration: none;
border-bottom: 0;
font-size: 14px;
}
h4.inherited {
clear: left;
font-size: 15px;
font-style: italic;
}
pre.syntax {
margin-bottom: 5px;
}
ul.argument-list {
font-size: 12px;
padding: 0;
margin: 0;
}
ul.argument-list li {
line-height: 140%;
margin-top: 0px;
margin-bottom: 5px;
}
ul.argument-list li code {
font-size: 11px;
}
ul.argument-list .argument-name {
background-color: #eeffee;
border-color: #6b6;
}
/* SECTION ICONS */
.page-content .section .section-title h3 {
padding-right: 24px;
background-position: right 0;
background-repeat: no-repeat;
}
.section-constructor .section-title h3 {
background-image: url(../images/constructor.png);
}
.section-method-list .section-title h3 {
background-image: url(../images/method.png);
}
.section-klass_methods .section-title h3 {
background-image: url(../images/class_method.png);
}
.section-klass_properties .section-title h3 {
background-image: url(../images/class_property.png);
}
.section-instance_methods .section-title h3 {
background-image: url(../images/instance_method.png);
}
.section-instance_properties .section-title h3 {
background-image: url(../images/instance_property.png);
}
.section-mixins .section-title h3 {
background-image: url(../images/mixin.png);
}
.section-classes .section-title h3 {
background-image: url(../images/class.png);
}
.section-namespaces .section-title h3 {
background-image: url(../images/namespace.png);
}
.section-sections .section-title h3 {
background-image: url(../images/section.png);
}
.section-utilities .section-title h3 {
background-image: url(../images/utility.png);
}
.section-description .section-title h3 {
background-image: url(../images/description.png);
}
.section-subclasses .section-title h3 {
background-image: url(../images/subclass.png);
}
.section-superclass .section-title h3 {
background-image: url(../images/superclass.png);
}
/* notes */
p.note,
p.alias,
p.related-to {
font-size: 11px;
line-height: 14px;
padding: 5px 20px 5px 60px;
background-repeat: no-repeat;
background-position: 20px 50%;
border: 1px solid #000;
}
p.note {
background-color: #f0f0f4;
background-image: url(../images/information.png);
border-color: #69c;
}
p.alias {
background-color: #fff6de;
background-image: url(../images/alias.png);
border-color: #cc9;
}
p.related-to {
background-color: #f4f0f4;
background-image: url(../images/related_to.png);
border-color: #c9c;
}
/* halo around selected method */
.highlighter {
border: 3px solid #69f;
z-index: -1;
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
}
/* 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-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-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;
padding: 3px 10px 3px 28px;
background-position: 6px 50%;
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,
#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);
}
.menu-item a.mixin {
background-image: url(../images/mixin.png);
}
.menu-item a.utility {
background-image: url(../images/utility.png);
}
.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-section {
margin-bottom: 1.0em;
}
#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;
}
#search {
width: 200px;
padding: 2px 3px;
}
input.ghosted {
font-style: italic;
color: #aaa;
}
/* FOOTER */
#footer {
color: #bbb;
width: 595px;
margin-left: 120px;
margin-top: 30px;
}
#footer .content {
border-top: 1px solid #ccc;
margin: 0;
padding: 30px 0;
}
#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;
}

View File

@ -1,32 +0,0 @@
module PDoc
module Generators
module Html
module Helpers
module BaseHelper
end
module LinkHelper
end
module CodeHelper
end
module MenuHelper
def menu(obj)
class_names = menu_class_name(obj)
li_class_names = obj.type == "section" ? "menu-section" : ""
html = <<-EOS
<div class='menu-item'>
#{auto_link(obj, false, :class => class_names_for(obj))}
</div>
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

View File

@ -1,24 +0,0 @@
<% @title = "Home" %>
<div class="page-introduction">
<%= @index_page_content %>
</div> <!-- .section -->
<div class="section section-sections">
<div class="section-title">
<h3>Sections</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="section-list">
<% @root.sections.each do |section| %>
<li>
<h4>
<a href="<%= path_to_section(section) %>"><%= section.name %></a>
</h4>
<p><%= htmlize(section.short_description) %></p>
</li>
<% end %>
</ul>
</div> <!-- .section-content -->
</div> <!-- .section -->

View File

@ -1,6 +0,0 @@
if (!window.PDoc) window.PDoc = {};
PDoc.elements = {
<%= @root.map { |e, i|
"'#{e.full_name}': { 'name': '#{e.full_name}', 'type': '#{e.type}', 'path': '#{path_to(e)}' }" }.join(",\n")
%>
};

View File

@ -1,86 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Prototype API documentation | <%= @title %></title>
<meta name="generator" content="PDoc" />
<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "application", "code_highlighter", "tabs" %>
<%= javascript_include_tag "item_index" %>
<%= stylesheet_link_tag "api" %>
<script type="text/javascript">
PDoc.pathPrefix = '<%= path_prefix %>';
</script>
</head>
<body>
<div id="sidebar">
<ul id="sidebar_tabs" class="sidebar-tabs">
<li>
<a href="#menu_pane">Menu</a>
</li>
<li>
<a href="#search_pane">Search</a>
</li>
</ul> <!-- .sidebar-tabs -->
<form class="search-ribbon">
<label>
<span class="hidden">Search</span>
<input type="text" id="search" size="20" title="Search" />
</label>
</form>
<div class="sidebar-pane scrollable" id="menu_pane">
<ul id="api_menu" class="menu-items">
<% @root.sections.each do |section| %>
<%= menu(section) %>
<% end %>
</ul> <!--- #api_menu =-->
</div> <!-- .sidebar-pane -->
<div class="sidebar-pane" id="search_pane">
<ul id="search_results" class="search-results menu-items scrollable"></ul>
</div> <!-- .sidebar-pane -->
</div> <!-- #sidebar -->
<div id="page">
<div class="masthead">
<div class="content">
<h1 class="logo">
<a href="http://prototypejs.org"><span class="replaced">Prototype JavaScript framework</span></a>
</h1>
</div> <!-- .content -->
</div> <!-- .masthead -->
<div id="main" class="page-content">
<%= @content_for_layout %>
</div> <!-- #main -->
<div id="footer">
<div class="content clearfix">
<div class="cc clearfix">
<span>
<img src="http://creativecommons.org/images/public/somerights20.png"
style="border-width: 0pt;" alt="Creative Commons License"/>
</span>
<p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0 Unported License</a>.</p>
</div> <!-- .cc -->
<div class="credits">
Generated by <a href="http://pdoc.org">PDoc</a>. Uses <a href="http://famfamfam.com/lab/icons/silk/" title="famfamfam.com: Silk Icons">Silk Icons</a> and portions of <a href="http://github.com/280north/aristo/tree/master" title="280north's aristo at master - GitHub">Aristo</a>.
</div> <!-- .credits -->
</div> <!-- .content -->
</div> <!-- #footer -->
</div> <!-- #page -->
</body>
</html>

View File

@ -1,171 +0,0 @@
<% d = @doc_instance %>
<% @title = "#{d.full_name} #{d.type}" %>
<%= include "partials/breadcrumbs", :object => d %>
<h2 class="page-title <%= class_names_for(d) %>">
<span class="type"><%= d.type %> </span><%= d.full_name %>
</h2>
<% # Does it have a description? %>
<% if d.description && !d.description.empty? %>
<div class="section section-description">
<div class="section-title">
<h3>Description</h3>
</div> <!-- .section-title -->
<div class="section-content">
<%= htmlize(d.description) %>
</div> <!-- .section-content -->
</div> <!--.section -->
<% end %>
<% # Is it a CLASS? %>
<% if d.is_a?(Documentation::Klass) %>
<% if d.superklass %>
<div class="section section-superclass">
<div class="section-title">
<h3>Superclass</h3>
</div> <!-- .section-title -->
<div class="section-content">
<p><%= auto_link_code(d.superklass, false) %></p>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% unless d.subklasses.empty? %>
<div class="section section-subclasses">
<div class="section-title">
<h3>Subclasses</h3>
</div> <!-- .section-title -->
<div class="section-content">
<p><%= d.subklasses.map { |s| auto_link_code(s, false) }.join(', ') %></p>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% end %>
<% # Does it have MIXINS? %>
<% unless d.mixins.compact.empty? %>
<div class="section section-mixins">
<div class="section-title">
<h3>Includes</h3>
</div> <!-- .section-title -->
<div class="section-content">
<p><%= d.mixins.map { |m| auto_link_code(m, false) }.join(', ') %></p>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% # Are there related utilities? %>
<% unless d.related_utilities.empty? %>
<div class="section section-utilities">
<div class="section-title">
<h3>Related utilities</h3>
</div> <!-- .section-title -->
<div class="section-content">
<p><%= d.related_utilities.map { |u| auto_link_code(u, false) }.join(', ') %></p>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% # List its methods. %>
<% unless d.all_methods.empty? && d.mixins.empty? %>
<div class="section section-method-list">
<div class="section-title">
<h3>Methods</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-list">
<% d.all_methods.each do |method| %>
<li><%= auto_link_code(method, true, :class => class_names_for(method)) %></li>
<% end %>
</ul>
<% unless d.mixins.compact.empty? %>
<% d.mixins.each do |mixin| %>
<h4 class="inherited">Inherited from <%= auto_link(mixin) %></h4>
<ul class="method-list">
<% mixin.all_methods.each do |method| %>
<li><%= auto_link_code(method, true, :class => class_names_for(method)) %></li>
<% end %>
</ul>
<% end %>
<% end %>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% if d.is_a?(Documentation::Klass) && d.constructor %>
<div class="section section-constructor">
<div class="section-title">
<h3>Constructor</h3>
</div> <!-- .section-title -->
<div class="section-content">
<pre id="<%= dom_id(d.constructor) %>" class="syntax"><code><%= d.constructor.ebnf_expressions.join("\n").strip %></code></pre>
<% unless d.constructor.arguments.empty? %>
<ul class="argument-list">
<% d.constructor.arguments.each do |arg| %>
<li>
<code><%= arg.name %></code>
<% unless arg.types.empty? %>
(<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>)
<% end %>
<%= ' &ndash; ' + inline_htmlize(arg.description) unless arg.description.empty? %>
</li>
<% end %>
</ul> <!-- .argument-list -->
<% end %>
<p><%= htmlize(d.constructor.description) %></p>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% 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? %>
<div class="section section-<%= method.to_s %>">
<div class="section-title">
<h3><%= title %></h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-details-list">
<%= include "partials/short_description", :collection => methods %>
</ul>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% end %>

View File

@ -1,6 +0,0 @@
<ul class="breadcrumbs clearfix">
<li><a href="<%= path_prefix %>index.html">Home</a> &rarr;</li>
<% unless object.is_a?(Documentation::Section) %>
<li><a href="<%= path_to(object.section) %>"><%= object.section.name %></a> &rarr;</li>
<% end %>
</ul>

View File

@ -1,48 +0,0 @@
<li class="method-description">
<h4 id="<%= dom_id(object) %>"><%= object.name %>
<span class="method-permalink"><a href="<%= path_to(object) %>">#</a></span></h4>
<% if object.signature %>
<%= method_synopsis(object) %>
<% end %>
<% if object.is_a?(Documentation::Method) %>
<% unless object.arguments.empty? %>
<ul class="argument-list">
<% object.arguments.each do |arg| %>
<li>
<code class="argument-name"><%= arg.name %></code>
<% unless arg.types.empty? %>
<span class="argument-types">(<%= arg.types.map { |t| auto_link_code(t, false) }.join(' | ') %>)</span>
<% end %>
<%= ' &ndash; ' + inline_htmlize(arg.description) unless arg.description.empty? %>
</li>
<% end %>
</ul> <!-- .argument-list -->
<% end %>
<% end %>
<%= htmlize(object.description) %>
<% # Does it have an alias? %>
<% unless object.aliases.empty? %>
<p class="alias aliases">Aliased as: <%= object.aliases.map { |a| auto_link_code(a, false) }.join(', ') %></p>
<% end %>
<% # Is it an alias of something else? %>
<% if object.alias? %>
<p class="alias alias-of">Alias of: <%= auto_link_code(object.alias_of, false) %></p>
<% end %>
<% # Is it related to something? %>
<% if object.is_a?(Documentation::Utility) && object.related_to %>
<p class="related-to">Related to:
<%= auto_link_code(object.related_to, false) %></p>
<% end %>
<% # Is it methodized? %>
<% if object.is_a?(Documentation::Method) && object.methodized? %>
<p class="note">This method can be called <em>either</em> as an instance method <em>or</em> as a generic method. If calling as a generic, pass the instance in as the first argument.</p>
<% end %>
</li>

View File

@ -1,99 +0,0 @@
<% @title = "#{@doc_instance.full_name}" %>
<% section = @doc_instance %>
<%= include "partials/breadcrumbs", :object => section %>
<h2 class="page-title">
<span class="type">Section </span><%= section.name %>
</h2>
<div class="section section-description">
<div class="section-title">
<h3>Description</h3>
</div> <!-- .section-title -->
<div class="section-content">
<%= htmlize(section.description) %>
</div> <!-- .section-content -->
</div> <!--.section -->
<% # Iterate over the items in this section. %>
<% utilities = section.utilities %>
<% unless utilities.empty? %>
<div class="section section-utilities">
<div class="section-title">
<h3>Utilities</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-list">
<% utilities.each do |utility| %>
<li><%= auto_link_code(utility) %></li>
<% end %>
</ul>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% namespaces = section.namespaces %>
<% unless namespaces.empty? %>
<div class="section section-namespaces">
<div class="section-title">
<h3>Namespaces</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-details-list">
<% namespaces.each do |namespace| %>
<li class="method-description">
<h4>
<a href="<%= path_to(namespace) %>"><%= namespace.full_name %></a>
</h4>
<% unless namespace.short_description.nil? %>
<p><%= htmlize(namespace.short_description) %></p>
<% end %>
</li>
<% end %>
</ul>
</div> <!-- .section-content -->
</div> <!-- .section -->
<% end %>
<% klasses = section.klasses %>
<% unless klasses.empty? %>
<div class="section section-classes">
<div class="section-title">
<h3>Classes</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-details-list">
<% klasses.each do |klass| %>
<li class="method-description">
<h4>
<a href="<%= path_to(klass) %>"><%= klass.full_name %></a>
</h4>
<% unless klass.short_description.nil? %>
<p><%= htmlize(klass.short_description) %></p>
<% end %>
</li>
<% end %>
</ul>
</div> <!-- .section-content -->
</div> <!--.section -->
<% end %>

View File

@ -1,21 +0,0 @@
<% d = @doc_instance %>
<% @title = "#{@doc_instance.full_name} utility" %>
<%= include "partials/breadcrumbs", :object => d %>
<h2 class="page-title">
<span class="type">utility </span><%= @doc_instance.name %>
</h2>
<div class="section">
<div class="section-title">
<h3>Description</h3>
</div> <!-- .section-title -->
<div class="section-content">
<ul class="method-details-list">
<%= include "partials/short_description", :collection => [@doc_instance] %>
</ul>
</div> <!-- .section-content -->
</div> <!-- .section -->

View File

@ -951,6 +951,9 @@ new Test.Unit.Runner({
// height/width could always be calculated if it's set to "auto" (Firefox)
this.assertNotNull($('auto_dimensions').getStyle('height'));
this.assertNotNull($('auto_dimensions').getStyle('width'));
// assert that using getStyle on an object without a style property returns null
this.assertNull(Element.Methods.getStyle({}, 'width'));
},
testElementGetOpacity: function() {
@ -1322,6 +1325,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() {

View File

@ -1,130 +0,0 @@
<!-- Absolutely-positioned element with BODY as offset parent. -->
<div id="box1">
<p>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.</p>
</div> <!-- #box1 -->
<style type="text/css" media="screen">
#box1 {
position: absolute;
top: 1020px;
left: 25px;
width: 242px;
height: 555px;
padding: 10px;
margin: 5px;
border: 3px solid #000;
}
</style>
<!-- Hidden, statically-positioned element. -->
<div id="box2">
<p>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.</p>
</div> <!-- #box2 -->
<style type="text/css" media="screen">
#box2 {
display: none;
width: 500px;
padding: 10px;
margin: 5px;
border: 3px solid #000;
}
</style>
<!-- Hidden, statically positioned element with width constrained by parent. -->
<div id="box3_parent">
<div id="box3">
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.
</div> <!-- #box3 -->
</div> <!-- #box3_parent -->
<style type="text/css" media="screen">
/* When an element itself has display: none, we can still figure out what
its dimensions will be _when_ it is made visible. */
#box3_parent {
width: 400px;
}
#box3 {
display: none;
padding: 10px;
margin: 5px;
border: 3px solid #000;
}
</style>
<!-- Deeply hidden, statically-positioned element with width constrained by ancestor. -->
<div id="box4_ancestor">
<div id="box4_parent">
<div id="box4">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</div> <!-- #box4 -->
</div> <!-- #box4_parent -->
</div> <!-- #box4_ancestor -->
<style type="text/css" media="screen">
/* But when an _ancestor_ of an element has display: none, the situation
is too complex for us to speculate. Instead, we'll return `0` for any
height/width measurements. */
#box4_ancestor {
width: 600px;
}
#box4_parent {
display: none;
}
#box4 {
padding: 13px;
margin: 2px;
border: none;
}
</style>
<!-- Absolutely-positioned element with non-BODY offset parent and positioning by percentage. -->
<div id="box5_ancestor">
<div id="box5_parent">
<div id="box5">
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</div> <!-- #box5 -->
</div> <!-- #box5_parent -->
</div> <!-- #box5_ancestor -->
<style type="text/css" media="screen">
#box5_ancestor {
width: 600px;
}
#box5_parent {
position: relative;
}
#box5 {
position: absolute;
width: 200px;
top: 30px;
right: 10%;
}
</style>
<!-- Element positioned to the exact top-left corner of its parent. -->
<div id="box6_parent">
<div id="box6">
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div> <!-- #box6 -->
</div> <!-- #box6_parent -->
<style type="text/css" media="screen">
#box6_parent {
position: relative;
}
#box6 {
position: absolute;
left: 0;
top: 0;
}
</style>

View File

@ -79,4 +79,10 @@
<div id="counted_container"><div class="is_counted"></div></div>
<div foo-bar="baz" id="attr_with_dash">blah</div>
<div id="container_1" class="container">
<div id="container_2" class="container">
<span id="target_1"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
<div id="test_div_parent" class="test_class">
<div id="test_div_child" class="test_class">
</div>
</div>

View File

@ -1,89 +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({
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.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();
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();
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($('box3')), '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());
}
});

View File

@ -0,0 +1,50 @@
/*
<div id="test_div_parent" class="test_class">
<div id="test_div_child" class="test_class">
</div>
</div>
*/
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);
}
});

View File

@ -356,14 +356,6 @@ new Test.Unit.Runner({
this.assert(typeof results[2].show == 'function');
},
testCountedIsNotAnAttribute: function() {
var el = $('list');
Selector.handlers.mark([el]);
this.assert(!el.innerHTML.include("_counted"));
Selector.handlers.unmark([el]);
this.assert(!el.innerHTML.include("_counted"));
},
testCopiedNodesGetIncluded: function() {
this.assertElementsMatch(
Selector.matchElements($('counted_container').descendants(), 'div'),
@ -412,5 +404,11 @@ new Test.Unit.Runner({
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));
}
});

View File

@ -0,0 +1,803 @@
/* 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.LegacySelector = 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 = '<ul><li></li></ul><div><ul><li></li></ul></div>';
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.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES) return false;
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.LegacySelector._div.querySelector(this.expression);
} catch(e) {
return false;
}
return true;
},
compileMatcher: function() {
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.LegacySelector._cache[e]) {
this.matcher = Prototype.LegacySelector._cache[e];
return;
}
this.matcher = ["this.matcher = function(root) {",
"var r = root, h = Prototype.LegacySelector.handlers, c = false, n;"];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
p = ps[i].re;
name = ps[i].name;
if (m = e.match(p)) {
this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
new Template(c[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.matcher.push("return h.unique(n);\n}");
eval(this.matcher.join('\n'));
Prototype.LegacySelector._cache[this.expression] = this.matcher;
},
compileXPathMatcher: function() {
var e = this.expression, ps = Prototype.LegacySelector.patterns,
x = Prototype.LegacySelector.xpath, le, m, len = ps.length, name;
if (Prototype.LegacySelector._cache[e]) {
this.xpath = Prototype.LegacySelector._cache[e]; return;
}
this.matcher = ['.//*'];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
name = ps[i].name;
if (m = e.match(ps[i].re)) {
this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
new Template(x[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.xpath = this.matcher.join('');
Prototype.LegacySelector._cache[this.expression] = this.xpath;
},
findElements: function(root) {
root = root || document;
var e = this.expression, results;
switch (this.mode) {
case 'selectorsAPI':
// querySelectorAll queries document-wide, then filters to descendants
// of the context element. That's not what we want.
// Add an explicit context to the selector if necessary.
if (root !== document) {
var oldId = root.id, id = $(root).identify();
// Escape special characters in the ID.
id = id.replace(/([\.:])/g, "\\$1");
e = "#" + id + " " + e;
}
results = $A(root.querySelectorAll(e)).map(Element.extend);
root.id = oldId;
return results;
case 'xpath':
return document._getElementsByXPath(this.xpath, root);
default:
return this.matcher(root);
}
},
match: function(element) {
this.tokens = [];
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)) {
le = e;
for (var i = 0; i<len; i++) {
p = ps[i].re;
name = ps[i].name;
if (m = e.match(p)) {
// use the Prototype.LegacySelector.assertions methods unless the selector
// is too complex.
if (as[name]) {
this.tokens.push([name, Object.clone(m)]);
e = e.replace(m[0], '');
} else {
// reluctantly do a document-wide search
// and look for a match in the array
return this.findElements(document).include(element);
}
}
}
}
var match = true, name, matches;
for (var i = 0, token; token = this.tokens[i]; i++) {
name = token[0], matches = token[1];
if (!Prototype.LegacySelector.assertions[name](element, matches)) {
match = false; break;
}
}
return match;
},
toString: function() {
return this.expression;
},
inspect: function() {
return "#<Prototype.LegacySelector:" + this.expression.inspect() + ">";
}
});
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.LegacySelector.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.LegacySelector, {
_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.LegacySelector.xpath.operators[m[2]]).evaluate(m);
},
pseudo: function(m) {
var h = Prototype.LegacySelector.xpath.pseudos[m[1]];
if (!h) return '';
if (Object.isFunction(h)) return h(m);
return new Template(Prototype.LegacySelector.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.LegacySelector.patterns,
x = Prototype.LegacySelector.xpath, le, v, len = p.length, name;
var exclusion = [];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i = 0; i<len; i++) {
name = p[i].name;
if (m = e.match(p[i].re)) {
v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
exclusion.push("(" + v.substring(1, v.length - 1) + ")");
e = e.replace(m[0], '');
break;
}
}
}
return "[not(" + exclusion.join(" and ") + ")]";
},
'nth-child': function(m) {
return Prototype.LegacySelector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
},
'nth-last-child': function(m) {
return Prototype.LegacySelector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
},
'nth-of-type': function(m) {
return Prototype.LegacySelector.xpath.pseudos.nth("position() ", m);
},
'nth-last-of-type': function(m) {
return Prototype.LegacySelector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
},
'first-of-type': function(m) {
m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-of-type'](m);
},
'last-of-type': function(m) {
m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-last-of-type'](m);
},
'only-of-type': function(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;
if (formula == 'even') formula = '2n+0';
if (formula == 'odd') formula = '2n+1';
if (mm = formula.match(/^(\d+)$/)) // digit only
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;
predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
"((#{fragment} - #{b}) div #{a} >= 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.LegacySelector.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.LegacySelector.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.LegacySelector.handlers.unmark(results);
},
// COMBINATOR FUNCTIONS
descendant: function(nodes) {
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.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);
}
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.LegacySelector.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.LegacySelector.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.LegacySelector.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.LegacySelector.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.LegacySelector.handlers.byClassName(nodes, root, className);
},
byClassName: function(nodes, root, className) {
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;
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.LegacySelector.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.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.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.LegacySelector.handlers.nextElementSibling(node)) continue;
results.push(node);
}
return results;
},
'only-child': function(nodes, value, root) {
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.LegacySelector.pseudos.nth(nodes, formula, root);
},
'nth-last-child': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true);
},
'nth-of-type': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, false, true);
},
'nth-last-of-type': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true, true);
},
'first-of-type': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, false, true);
},
'last-of-type': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, true, true);
},
'only-of-type': function(nodes, formula, root) {
var p = Prototype.LegacySelector.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.LegacySelector.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.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);
}
}
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.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);
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.LegacySelector.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.LegacySelector.matchElements(elements, expression || '*')[index || 0];
},
findChildElements: function(element, expressions) {
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.LegacySelector(expressions[i].strip());
h.concat(results, selector.findElements(element));
}
return (l > 1) ? h.unique(results) : results;
}
});
if (Prototype.Browser.IE) {
Object.extend(Prototype.LegacySelector.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;
}
});
}

View File

@ -0,0 +1,18 @@
//= require "repository/legacy_selector"
Prototype.Selector = (function(engine) {
function select(selector, scope) {
return engine.findChildElements(scope || document, [selector]);
}
function match(element, selector) {
return !!engine.findElement([element], selector);
}
return {
engine: engine,
select: select,
match: match,
filter: engine.matchElements
};
})(Prototype.LegacySelector);

1
vendor/nwmatcher/repository vendored Submodule

@ -0,0 +1 @@
Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664

18
vendor/nwmatcher/selector_engine.js vendored Normal file
View File

@ -0,0 +1,18 @@
Prototype._original_property = window.NW;
//= require "repository/src/nwmatcher"
Prototype.Selector = (function(engine) {
function select(selector, scope) {
return engine.select(selector, scope || document, null, Element.extend);
}
return {
engine: engine,
select: select,
match: engine.match
};
})(NW.Dom);
// Restore globals.
window.NW = Prototype._original_property;
delete Prototype._original_property;

2
vendor/pdoc vendored

@ -1 +1 @@
Subproject commit e976b844144666113008a9159fa501f8f575bd1c
Subproject commit 472a55dd0019acf034d4f72522915a5e9efd0a1a

1
vendor/sizzle/repository vendored Submodule

@ -0,0 +1 @@
Subproject commit 415e466f70e5a53f589161b1f2944e5485007409

34
vendor/sizzle/selector_engine.js vendored Normal file
View File

@ -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;