Compare commits

..

34 Commits

Author SHA1 Message Date
Andrew Dupont
3ad847ce4b Fix issue where certain versions of Safari treat class names case-insensitively in Selector/$$ queries. [#390 state:resolved] 2008-11-18 23:26:22 -06:00
Andrew Dupont
9f09a8c791 Refactored class.js. 2008-11-18 22:28:06 -06:00
Andrew Dupont
275a344173 Deleted generated unit test files and added them to .gitignore. 2008-11-18 22:21:27 -06:00
Nick Stakenburg
d12e77c56d Fixed event.js so more unit tests pass on IE. Changed the use of '_' prefix to match other parts of the rewrite branch. 2008-10-26 16:19:37 -05:00
Andrew Dupont
10573fb1a9 Updated .gitignore to ignore files generated by rake tasks. 2008-10-26 16:16:02 -05:00
Andrew Dupont
62a1f7e2ce Fix issue where Function#argumentNames returned incorrect results in IE when comments were intermixed with argument names. [#397 state:resolved] 2008-10-26 16:15:51 -05:00
Andrew Dupont
0cc28be265 Reorganized unit tests to match the file structure of the source. 2008-10-26 15:28:56 -05:00
Andrew Dupont
be44ca0089 Merge branch 'rewrite' of git@github.com:sstephenson/prototype into rewrite 2008-10-02 01:01:48 -05:00
Andrew Dupont
b878bfe3ff Refactored event.js. 2008-10-02 01:00:05 -05:00
Tobie Langel
cd0734b8a7 string.js rewrite. 2008-10-01 15:08:05 +02:00
Tobie Langel
f788cf7048 Revert "Variables renaming in function.js for readability."
This reverts commit 160f85f518.
2008-10-01 15:06:03 +02:00
Tobie Langel
ba30188e5d string.js rewrite. 2008-10-01 15:01:35 +02:00
Tobie Langel
160f85f518 Variables renaming in function.js for readability. 2008-10-01 01:46:48 +02:00
Tobie Langel
d9087c26a9 function.js: rename private 'combine' method to 'update', add a private 'merge' methos. 2008-10-01 01:36:03 +02:00
Tobie Langel
3a93b6ccae Improve performance in function.js by using Array#slice directly. 2008-10-01 00:58:33 +02:00
Tobie Langel
d55932fc8c function.js rewrite. 2008-09-30 10:12:54 +02:00
Tobie Langel
ec5069ed3a Refactor object.js. 2008-09-30 01:04:25 +02:00
Tobie Langel
fee81bffee Fix typo in file name. 2008-09-30 00:52:45 +02:00
Tobie Langel
15d4d5fe7a Fixed conflicting merge. 2008-09-30 00:48:16 +02:00
Tobie Langel
5607889e41 Fix a bug introduced in my previous commit on protodoc.rb 2008-09-30 00:05:23 +02:00
Tobie Langel
9af3c039e1 Split ajax.js 2008-09-30 00:05:23 +02:00
Tobie Langel
8d3911ec09 Split src code into lang, dom and ajax directories. 2008-09-30 00:05:23 +02:00
Tobie Langel
e3f3e95b84 Split base.js up. 2008-09-30 00:05:22 +02:00
Tobie Langel
373ef2d839 Extract the template class out of string.js and into its own file . 2008-09-30 00:05:22 +02:00
Tobie Langel
dc70b13aed Reorganize range.js. 2008-09-30 00:05:22 +02:00
Tobie Langel
f64c9568c8 Reorganize number.js. 2008-09-30 00:05:22 +02:00
Tobie Langel
1669e41cda Reorganize hash.js. 2008-09-30 00:05:22 +02:00
Tobie Langel
bcbdf3b247 Reorganize enumerable.js. 2008-09-30 00:05:22 +02:00
Tobie Langel
22f9cfc626 More clean-up to array.js. 2008-09-30 00:05:22 +02:00
Tobie Langel
e63f50aa60 Reorganizing array.js source code. 2008-09-30 00:05:22 +02:00
Tobie Langel
a94c420a34 Reorganize hash.js. 2008-09-28 19:41:53 +02:00
Tobie Langel
b7e817927e Reorganize enumerable.js. 2008-09-28 19:25:03 +02:00
Tobie Langel
f5d8a093f2 More clean-up to array.js. 2008-09-28 19:14:17 +02:00
Tobie Langel
f46a3c2430 Reorganizing array.js source code. 2008-09-28 18:57:01 +02:00
78 changed files with 3572 additions and 9031 deletions

4
.gitignore vendored
View File

@ -1,5 +1,3 @@
.DS_Store
pkg
test/unit/tmp/*
doc
tmp
test/unit/tmp/*

20
.gitmodules vendored
View File

@ -1,20 +0,0 @@
[submodule "vendor/unittest_js"]
path = vendor/unittest_js
url = git://github.com/tobie/unittest_js.git
[submodule "vendor/caja_builder"]
path = vendor/caja_builder
url = git://github.com/tobie/unittest_js_caja_builder.git
[submodule "vendor/pdoc"]
path = vendor/pdoc
url = git://github.com/tobie/pdoc.git
[submodule "vendor/sprockets"]
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

174
CHANGELOG
View File

@ -1,177 +1,7 @@
* 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)
* Rewrite String#camelize using String#replace with a replacement function (Phred, John-David Dalton, Samuel Lebeau, kangax)
*1.6.1* (August 24, 2009)
* Avoid triggering a warning when Java is disabled in IE8. [#668 state:resolved] (orv, kangax, Andrew Dupont, Tobie Langel)
* Simplify String#(un)escapeHTML and remove their DOM dependencies. (Tobie Langel)
* Update UnittestJS. Modifiy test template accordingly. (Tobie Langel)
* Remove redundant if statement in Element#readAttribute. (Tobie Langel)
* Add missing semicolons. [#751 state:resolved] (Diego Perini)
* Remove expensive (for such low-level method) internal `getClass` in favor of plain string comparison (kangax)
* Fix `PeriodicalExecuter` so that it no longer suppresses exceptions. [#696 state:resolved] (Samuel Lebeau, Yaffle)
* Fix issue related to escaping of selectors for querySelectorAll. [#559 state:resolved] (Jorn Holm)
*1.6.1_rc3* (June 16, 2009)
* Fixed a variety of non-ASCII chars and similar [#610 state:resolved] (T.J. Crowder)
* Add Chrome 1+ to the list of supported browsers. (kangax)
* Fix `Template#evaluate` "eating" previous character if `null` was returned from `toTemplateReplacements` function. (Nir, Jürgen Hörmann, kangax)
* Make sure (deficient) APPLET, OBJECT and EMBED elements are extended with simulated methods in IE8. Return early if `_extendedByPrototype` is present on an element. (Tobie Langel, kangax)
* Replace array creation and `Array#include` with a more efficient `RegExp#test`. (kangax)
* Reorganize the way `ElementExtensions` are defined. Make sure elements used in SpecificElementExtensions are cleaned up. (kangax)
* Make sure $A works with primitive values. (mr_justin, kangax)
* Do not browser sniff when forking `unmark` function in selector suite. Instead use a proper test - PROPERTIES_ATTRIBUTES_MAP. (kangax)
* Do not use short-hand element methods notation (@element.getStyle() -> Element.getStyle(@element)) for performance reasons. Do not use `$A` and `Array.prototype.shift` when `Array.prototype.slice` can be used instead. (kangax)
* `Prototype.Browser.Opera` now uses stronger inference and is determined by [[Class]] of `window.opera` being - "Opera". (kangax)
* Fix error in event.js which prevented attaching more than one responder for an event name/element combination. [#651 state:resolved] (Rob Lineweaver)
* Do not sniff when testing for IE's proprietary mouseenter/mouseleave events support. Use more robust inference instead. (kangax)
* Use `Prototype.emptyFunction` consistently throughout unit tests. [#253 state:resolved] (Michael M Slusarz, John David Dalton, kangax)
* deprecation extension: mark Array#reduce() as removed. [#569 state:resolved] (Tobie Langel)
* `Form.serialize` now works safely with forms that have "length"-named elements. [#77 state:resolved] (Peter Adrianov, John-David Dalton, kangax)
*1.6.1_rc1* (March 22, 2009)
* `Element#update` now takes care of SCRIPT elements in IE. [#573 state:resolved] (Martin, Tobie Langel, kangax)
* Remove unused local variables from `Element.extend`. Fix one of the form tests to remove `_extendedByPrototype` by setting it to `undefined` rather than `false` (`_extendedByPrototype` being `false` does not force `Element.extend` to re-extend element). (T.J. Crowder, kangax)
* Make test for `escapeHTML`/`unescapeHTML` more strict. (Chrome 1.x escapes "<" and "&" with `innerHTML`, but not ">") (kangax)
* Remove another sniffing from one of DOM tests. Fixes last IE8 failure. (kangax)
* `Element.extend` now takes care of IE8 bug when HTMLAppletElement and HTMLObjectElement objects do not inherit from `Element.prototype`. (kangax)
* Fix DOM tests to use proper feature test when testing `setOpacity` (kangax)
* Fix another failure in IE8, `for`/`htmlFor` {get/set}Attribute translation. (kangax)
* Fix `Element#writeAttribute` and `Element#readAttribute` failures in IE8 due to lack of proper feature testing. (kangax)
* Remove sniffing from one of the DOM tests, which produced failures in IE8. (kangax)
* Fix `Form.reset` test where `respondsTo` wouldn't detect a method due to typeof returning "object" (rather than "function") in IE (kangax)
* Remove Array#reduce which currently overrides native `reduce` in clients implementing JS1.8, e.g. Firefox 3+ (Tobie Langel, Andrew Dupont, kangax)
* Make sure try/catch/finally is used instead of try/finally for clients without support for the latter one (e.g. Blackberry, IE) (Ville Koskinen, kangax)
* Use `in` operator when accessing property of a nodelist to prevent Safari <=2.0.4 from crashing (kangax)
* Add Element#clone as a safe wrapper of native `cloneNode`. (Andrew Dupont, kangax)
* Add tests to ensure IE8 properly assigns a class name in the `Element` constructor. [#529 state:resolved] (Riki Fridrich, Andrew Dupont)
* Remove sniffing from `Element` when detecting broken `setAttribute` in IE. [#571 state:resolved] (kangax)
* Remove sniffing from `Element.update` branching in favor of feature detection. [#574 state:resolved] (kangax)
* Remove sniffing when branching `escapeHTML` and `unescapeHTML`. [#570 state:resolved] (kangax)
* Redefine Element#down in IE 6-7 to avoid extending all descendants when no selector is given. [#452 state:resolved] (eno, Andrew Dupont)
* Reverse the definitions of Event#pointer(X|Y) and Event#pointer to prevent unnecessary computation. [#403 state:resolved] (Nick Stakenburg, Andrew Dupont)
* Add first-class support for `mouseenter` and `mouseleave` events in non-IE browsers (IE supports them natively). [#350 state:resolved] (Nick Stakenburg, Andrew Dupont)
* Make sure `_extendedByPrototype`, `_countedByPrototype`, and `prototypeUID` node expandos are accessed with `typeof` to prevent errors in some environments. [#354 state:resolved] (Hilberty, kangax, Andrew Dupont)
* Fix issue where Opera 9.x returns incorrect results on certain Selector queries with descendant combinators. [#395 state:resolved] (Arpan, fearphage, kangax, Andrew Dupont)
* Null out references to elements in cache on page unload. Need this in addition to the Event#stopObserving calls to clean up memory leaks. [#425 state:resolved] (ykphuah, mr_justin, Andrew Dupont)
* Ensure `toString` and `valueOf` properties are copied to a subclass only when necessary in IE6. [#382 state:resolved] (Samuel Lebeau)
* Make sure `getAttribute` is used without flag when accessing the "type" attribute of an iframe (IE throws error otherwise). [#118 state:resolved] (Zekid, kangax)
* String#gsub should escape RegExp metacharacters when the first argument is a string. [#469 state:resolved] (michael, kangax)
* Fix order of replacement in String#unescapeHTML [#544 state:resolved] (SWeini, kangax)
* Fix issue where a Selector query rooted on a node that had not been attached to the document failed in IE. [#464 state:resolved] (jddalton, kangax, Douglas Fraser, Andrew Dupont)
* Fix Selector to match elements with attributes containing hyphens. [#285 state:resolved] (leiyou, jddalton, kangax)
* Make sure Form.reset always returns a reference to the receiver element. [#309 state:resolved] (Phil, kangax)
* Escape ":" and "." characters when doing contextual CSS selection in browsers that support querySelectorAll. [#559 state:resolved] (fxtentacle, Andrew Dupont)
* Ensure the `target` property on events is never undefined in IE. [#383 state:resolved] (Mathias Karstädt, Diego Perini, Andrew Dupont)
* Ensure Element#descendants always returns an array. [#373 state:resolved] (kangax)
* Don't switch fixed position elements to absolute in Element.getDimensions [#543 state:resolved] (James Wheare)
* Avoid infinite loops when calling String#sub with empty pattern [#534 state:resolved] (Samuel Lebeau)
* Switch to Sprockets for building the Prototype distfiles. (sam)
* Switch Object.is(Array|String|Number) to use the vastly-superior approach discovered by Juriy. (kangax)
* Further fix to ensure Object.is(String|Number) do not throw exceptions on host objects in IE. (grepmaster, kangax, Tobie Langel, Andrew Dupont)
* Ensure Enumerable#grep can handle strings with RegExp metacharacters. (Marton Kiss-Albert, kangax)
* Switch to the "doScroll approach" for the dom:loaded custom event. (javier, Diego Perini, Nick Stakenburg, Andrew Dupont)
* Optimize document.viewport.get(Dimensions|Width|Height). (Nick Stakenburg, Andrew Dupont)
* Fix issue where Object#isString and Object#isNumber return false for String and Number "wrapper" objects. (atrepp, Samuel Lebeau, Andrew Dupont)
* Set document.loaded = true before firing dom:loaded custom event. (Andrew Dupont)
* Allow Element#store to accept an object containing several key/value pairs. (ZenCocoon, Andrew Dupont)
* Change Element#store to return the element itself (for chaining). (Andrew Dupont)
* Add non-bubbling custom events. A new final argument to Element#fire defaults to `true`; pass `false` to prevent bubbling when firing a custom event. (Andrew Dupont)
* Alter event system to use new element storage API rather than have its own global hashtable. (Andrew Dupont)
* Add Element#store and Element#retrieve for safe, hash-backed storage of element metadata (no memory leaks). Also add Element#getStorage for working with the element's storage hash directly. Hat tip: Mootools. (ZenCocoon, Andrew Dupont)
* Fix issue where certain versions of Safari treat class names case-insensitively in Selector/$$ queries. (Andrew Dupont, kangax, Brice)
* Fix issue where Function#argumentNames returned incorrect results in IE when comments were intermixed with argument names. (Christophe Porteneuve, T.J. Crowder)
* Selector.patterns should be represented as an ordered structure. (ADO, kangax)
* Performance improvements in Function methods (Samuel Lebeau, kangax, jddalton, Tobie Langel).
*1.6.0.3* (September 29, 2008)
@ -358,13 +188,13 @@
* Fix the way Selector handles [pseudoclass + combinator] with no space in between. Closes #9696. [kangax, fearphage, Andrew Dupont]
* Optimize Element#up/down/next/previous. Closes #10353. [Dylan Bruzenak, Nick Stakenburg, Andrew Dupont]
* Optimize Element#up/down/next/previous. Closes #10353. [Dylan Bruzenak, Nick Stackenburg, Andrew Dupont]
* Handle case-sensitivity in Selector tag names better. Closes #5052. [mexx, Andrew Dupont]
*1.6.0.1* (December 4, 2007)
* Change document.viewport.getDimensions to exclude scrollbars in all cases. Closes #10148, #9288. [Nick Stakenburg]
* Change document.viewport.getDimensions to exclude scrollbars in all cases. Closes #10148, #9288. [Nick Stackenburg]
* Add logic to Element#getStyle in Opera that fixes inaccurate reporting of computed 'width' and 'height' properties. [Andrew Dupont]

View File

@ -1,63 +0,0 @@
Prototype
=========
#### An object-oriented JavaScript framework ####
Prototype is a JavaScript framework that aims to ease development of dynamic
web applications. It offers a familiar class-style OO framework, extensive
Ajax support, higher-order programming constructs, and easy DOM manipulation.
### Targeted platforms ###
Prototype currently targets the following platforms:
* Microsoft Internet Explorer for Windows, version 6.0 and higher
* Mozilla Firefox 1.5 and higher
* Apple Safari 2.0.4 and higher
* Opera 9.25 and higher
* Chrome 1.0 and higher
Using Prototype
---------------
To use Prototype in your application, download the latest release from the
Prototype web site (<http://prototypejs.org/download>) and copy
`dist/prototype.js` to a suitable location. Then include it in your HTML
like so:
<script type="text/javascript" src="/path/to/prototype.js"></script>
### Building Prototype from source ###
`prototype.js` is a composite file generated from many source files in
the `src/` directory. To build Prototype, you'll need:
* a copy of the Prototype source tree, either from a distribution tarball or
from the Git repository (see below)
* Ruby 1.8.2 or higher (<http://www.ruby-lang.org/>)
* Rake--Ruby Make (<http://rake.rubyforge.org/>)
* RDoc, if your Ruby distribution does not include it
From the root Prototype directory,
* `rake dist` will preprocess the Prototype source using Sprockets and
generate the composite `dist/prototype.js`.
* `rake package` will create a distribution tarball in the
`pkg/` directory.
Contributing to Prototype
-------------------------
Check out the Prototype source with
$ git clone git://github.com/sstephenson/prototype.git
$ cd prototype
$ git submodule init
$ git submodule update vendor/sprockets vendor/pdoc vendor/unittest_js
Find out how to contribute: <http://prototypejs.org/contribute>.
Documentation
-------------
Please see the online Prototype API: <http://api.prototypejs.org>.

56
README.rdoc Normal file
View File

@ -0,0 +1,56 @@
= Prototype
==== An object-oriented JavaScript framework
Prototype is a JavaScript framework that aims to ease development of dynamic
web applications. It offers a familiar class-style OO framework, extensive
Ajax support, higher-order programming constructs, and easy DOM manipulation.
=== Targeted platforms
Prototype currently targets the following platforms:
* Microsoft Internet Explorer for Windows, version 6.0 and higher
* Mozilla Firefox 1.5 and higher
* Apple Safari 2.0 and higher
* Opera 9.25 and higher
== Using Prototype
To use Prototype in your application, download the latest release from the
Prototype web site (http://prototypejs.org/download) and copy
<tt>dist/prototype.js</tt> to a suitable location. Then include it in your HTML
like so:
<script type="text/javascript" src="/path/to/prototype.js"></script>
=== Building Prototype from source
<tt>prototype.js</tt> is a composite file generated from many source files in
the <tt>src/</tt> directory. To build Prototype, you'll need:
* a copy of the Prototype source tree, either from a distribution tarball or
from the Git repository (see below)
* Ruby 1.8.2 or higher (http://www.ruby-lang.org/)
* Rake -- Ruby Make (http://rake.rubyforge.org/)
* RDoc, if your Ruby distribution does not include it
From the root Prototype directory,
* <tt>rake dist</tt> will preprocess the Prototype source using ERB and
generate the composite <tt>dist/prototype.js</tt>.
* <tt>rake package</tt> will create a distribution tarball in the
<tt>pkg/</tt> directory.
== Contributing to Prototype
Check out the Prototype source with
$ git clone git://github.com/sstephenson/prototype.git
Find out how to contribute:
http://prototypejs.org/contribute
== Documentation
Please see the online Prototype API:
http://prototypejs.org/api

289
Rakefile
View File

@ -1,183 +1,43 @@
require 'rake'
require 'rake/packagetask'
require 'yaml'
module PrototypeHelper
ROOT_DIR = File.expand_path(File.dirname(__FILE__))
SRC_DIR = File.join(ROOT_DIR, 'src')
DIST_DIR = File.join(ROOT_DIR, 'dist')
DOC_DIR = File.join(ROOT_DIR, 'doc')
TEMPLATES_DIR = File.join(ROOT_DIR, 'templates')
PKG_DIR = File.join(ROOT_DIR, 'pkg')
TEST_DIR = File.join(ROOT_DIR, 'test')
TEST_UNIT_DIR = File.join(TEST_DIR, 'unit')
TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp')
VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION']
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.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, options[:path]),
:load_path => load_path,
:source_files => [options[:source]],
:strip_comments => options[:strip_comments]
)
secretary.concatenation.save_to(options[:destination])
end
def self.build_doc_for(file)
mkdir_p TMP_DIR
temp_path = File.join(TMP_DIR, "prototype.temp.js")
sprocketize(
:path => 'src',
:source => file,
:destination => temp_path,
:selector_engine => ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE,
:strip_comments => false
)
rm_rf DOC_DIR
PDoc.run({
:source_files => [temp_path],
:destination => DOC_DIR,
:index_page => 'README.markdown',
:syntax_highlighter => :pygments,
:markdown_parser => :bluecloth
})
rm_rf temp_path
end
def self.require_sprockets
require_submodule('Sprockets', 'sprockets')
end
def self.require_pdoc
require_submodule('PDoc', 'pdoc')
end
def self.require_unittest_js
require_submodule('UnittestJS', 'unittest_js')
end
def self.require_caja_builder
require_submodule('CajaBuilder', 'caja_builder')
end
def self.get_selector_engine(name)
return if name == 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
# 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"
end
exit
end
end
end
PROTOTYPE_ROOT = File.expand_path(File.dirname(__FILE__))
PROTOTYPE_SRC_DIR = File.join(PROTOTYPE_ROOT, 'src')
PROTOTYPE_DIST_DIR = File.join(PROTOTYPE_ROOT, 'dist')
PROTOTYPE_PKG_DIR = File.join(PROTOTYPE_ROOT, 'pkg')
PROTOTYPE_TEST_DIR = File.join(PROTOTYPE_ROOT, 'test')
PROTOTYPE_TMP_DIR = File.join(PROTOTYPE_TEST_DIR, 'unit', 'tmp')
PROTOTYPE_VERSION = '1.6.0.3'
task :default => [:dist, :dist_helper, :package, :clean_package_source]
desc "Builds the distribution."
task :dist do
PrototypeHelper.sprocketize(
:path => 'src',
:source => 'prototype.js',
:selector_engine => ENV['SELECTOR_ENGINE'] || PrototypeHelper::DEFAULT_SELECTOR_ENGINE
)
end
namespace :doc do
desc "Builds the documentation."
task :build => [:require] do
PrototypeHelper.build_doc_for(ENV['SECTION'] ? "#{ENV['SECTION']}.js" : 'prototype.js')
end
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
task :require do
PrototypeHelper.require_pdoc
Dir.chdir(PROTOTYPE_SRC_DIR) do
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype.js'), 'w+') do |dist|
dist << Protodoc::Preprocessor.new('prototype.js')
end
end
end
task :doc => ['doc:build']
desc "Builds the updating helper."
task :dist_helper do
PrototypeHelper.sprocketize(:path => 'ext/update_helper', :source => 'prototype_update_helper.js')
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
Dir.chdir(File.join(PROTOTYPE_ROOT, 'ext', 'update_helper')) do
File.open(File.join(PROTOTYPE_DIST_DIR, 'prototype_update_helper.js'), 'w+') do |dist|
dist << Protodoc::Preprocessor.new('prototype_update_helper.js')
end
end
end
Rake::PackageTask.new('prototype', PrototypeHelper::VERSION) do |package|
Rake::PackageTask.new('prototype', PROTOTYPE_VERSION) do |package|
package.need_tar_gz = true
package.package_dir = PrototypeHelper::PKG_DIR
package.package_dir = PROTOTYPE_PKG_DIR
package.package_files.include(
'[A-Z]*',
'dist/prototype.js',
@ -187,87 +47,46 @@ Rake::PackageTask.new('prototype', PrototypeHelper::VERSION) do |package|
)
end
task :clean_package_source do
rm_rf File.join(PrototypeHelper::PKG_DIR, "prototype-#{PrototypeHelper::VERSION}")
end
desc "Builds the distribution and the test suite, runs the tests and collects their results."
task :test => [:dist, :test_units]
task :test => ['test:build', 'test:run']
namespace :test do
desc 'Runs all the JavaScript unit tests and collects the results'
task :run => [:require] do
testcases = ENV['TESTCASES']
browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',')
tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',')
runner = UnittestJS::WEBrickRunner::Runner.new(:test_dir => PrototypeHelper::TMP_DIR)
Dir[File.join(PrototypeHelper::TMP_DIR, '*_test.html')].each do |file|
file = File.basename(file)
test = file.sub('_test.html', '')
unless tests_to_run && !tests_to_run.include?(test)
runner.add_test(file, testcases)
end
require 'test/lib/jstest'
desc "Runs all the JavaScript unit tests and collects the results"
JavaScriptTestTask.new(:test_units => [:build_unit_tests]) do |t|
testcases = ENV['TESTCASES']
tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',')
browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',')
t.mount("/dist")
t.mount("/test")
Dir.mkdir(PROTOTYPE_TMP_DIR) unless File.exist?(PROTOTYPE_TMP_DIR)
Dir["test/unit/tmp/*_test.html"].each do |file|
test_name = File.basename(file).sub("_test.html", "")
unless tests_to_run && !tests_to_run.include?(test_name)
t.run("/#{file}", testcases)
end
UnittestJS::Browser::SUPPORTED.each do |browser|
unless browsers_to_test && !browsers_to_test.include?(browser)
runner.add_browser(browser.to_sym)
end
end
trap('INT') { runner.teardown; exit }
runner.run
end
task :build => [:clean, :dist] do
builder = UnittestJS::Builder::SuiteBuilder.new({
:input_dir => PrototypeHelper::TEST_UNIT_DIR,
:assets_dir => PrototypeHelper::DIST_DIR
})
selected_tests = (ENV['TESTS'] || '').split(',')
builder.collect(*selected_tests)
builder.render
%w( safari firefox ie konqueror opera chrome ).each do |browser|
t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser)
end
task :clean => [:require] do
UnittestJS::Builder.empty_dir!(PrototypeHelper::TMP_DIR)
end
task :require do
PrototypeHelper.require_unittest_js
end
end
task :test_units do
puts '"rake test_units" is deprecated. Please use "rake test" instead.'
end
task :build_unit_tests do
puts '"rake test_units" is deprecated. Please use "rake test:build" instead.'
Dir[File.join('test', 'unit', '*_test.js')].each do |file|
PageBuilder.new(file, 'prototype.erb').render
end
end
task :clean_package_source do
rm_rf File.join(PROTOTYPE_PKG_DIR, "prototype-#{PROTOTYPE_VERSION}")
end
desc 'Generates an empty tmp directory for building tests.'
task :clean_tmp do
puts '"rake clean_tmp" is deprecated. Please use "rake test:clean" instead.'
puts 'Generating an empty tmp directory for building tests.'
FileUtils.rm_rf(PROTOTYPE_TMP_DIR) if File.exist?(PROTOTYPE_TMP_DIR)
Dir.mkdir(PROTOTYPE_TMP_DIR)
end
namespace :caja do
task :test => ['test:build', 'test:run']
namespace :test do
task :run => ['rake:test:run']
task :build => [:require, 'rake:test:clean', :dist] do
builder = UnittestJS::CajaBuilder::SuiteBuilder.new({
:input_dir => PrototypeHelper::TEST_UNIT_DIR,
:assets_dir => PrototypeHelper::DIST_DIR,
:whitelist_dir => File.join(PrototypeHelper::TEST_DIR, 'unit', 'caja_whitelists'),
:html_attrib_schema => 'html_attrib.json'
})
selected_tests = (ENV['TESTS'] || '').split(',')
builder.collect(*selected_tests)
builder.render
end
end
task :require => ['rake:test:require'] do
PrototypeHelper.require_caja_builder
end
end

View File

@ -6,8 +6,8 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../dist/prototype.js" type="text/javascript"></script>
<script src="../../dist/prototype_update_helper.js" type="text/javascript"></script>
<script src="../../vendor/unittest_js/assets/unittest.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" href="../../vendor/unittest_js/assets/unittest.css" type="text/css" charset="utf-8" />
<script src="../../test/lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../test/test.css" type="text/css" />
</head>
<body>
<h1>Prototype Unit test file</h1>
@ -45,8 +45,8 @@
var actualType = log.type.beforeLast() || log.type.last();
this.assertEqual(expected, actualMessage, 'assertInfoNotified');
this.assertEqual('info', actualType, 'assertInfoNotified');
log.message.length = 0;
log.type.length = 0;
log.message.push(null);
log.type.push(null);
},
assertErrorNotified: function(expected) {
@ -54,8 +54,8 @@
var actualType = log.type.beforeLast() || log.type.last();
this.assertEqual(expected, actualMessage, 'assertErrorNotified');
this.assertEqual('error', actualType, 'assertErrorNotified');
log.message.length = 0;
log.type.length = 0;
log.message.push(null);
log.type.push(null);
},
assertWarnNotified: function(expected) {
@ -63,30 +63,21 @@
var actualType = log.type.beforeLast() || log.type.last();
this.assertEqual(expected, actualMessage, 'assertWarnNotified');
this.assertEqual('warn', actualType, 'assertWarnNotified');
log.message.length = 0;
log.type.length = 0;
log.message.push(null);
log.type.push(null);
},
assertNotNotified: function() {
this.assertEmpty(log.message, 'assertNotNotified');
this.assertEmpty(log.type, 'assertNotNotified');
log.message.length = 0;
log.type.length = 0;
},
assertEmpty: function(array, message) {
this.assertEqual(0, array.length, message || '');
this.assertNull(log.message.last(), 'assertNotNotified');
this.assertNull(log.type.last());
log.message.push(null);
log.type.push(null);
}
});
new Test.Unit.Runner({
testGetStack: function() {
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());
}
this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack());
},
testDisplay: function() {
@ -268,15 +259,6 @@
this.assertRespondsTo('toJSON', h)
},
testArray: function() {
var a = [0, 1, 2, 3];
a.reduce(function(){});
this.assertErrorNotified('Array#reduce is no longer supported.\n' +
'This is due to an infortunate naming collision with Mozilla\'s own implementation of Array#reduce which differs completely from Prototype\'s implementation (it\'s in fact similar to Prototype\'s Array#inject).\n' +
'Mozilla\'s Array#reduce is already implemented in Firefox 3 (as part of JavaScript 1.8) and is about to be standardized in EcmaScript 3.1.')
},
testClass: function() {
Class.create();
this.assertInfoNotified('The class API has been fully revised and now allows for mixins and inheritance.\n' +
@ -285,38 +267,6 @@
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"
<%= include "update_helper.js" %>
/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008-2009 Tobie Langel
/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008 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 DOES NOT WORK IN INTERNET EXPLORER
* THIS SCRIPT WORKS IN FIREFOX ONLY
*--------------------------------------------------------------------------*/
var prototypeUpdateHelper = new UpdateHelper([
@ -253,15 +253,6 @@ var prototypeUpdateHelper = new UpdateHelper([
type: 'warn'
},
{
methodName: 'reduce',
namespace: Array.prototype,
message: 'Array#reduce is no longer supported.\n' +
'This is due to an infortunate naming collision with Mozilla\'s own implementation of Array#reduce which differs completely from Prototype\'s implementation (it\'s in fact similar to Prototype\'s Array#inject).\n' +
'Mozilla\'s Array#reduce is already implemented in Firefox 3 (as part of JavaScript 1.8) and is about to be standardized in EcmaScript 3.1.',
type: 'error'
},
{
methodName: 'unloadCache',
namespace: Event,
@ -275,62 +266,6 @@ 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'
}
]);
@ -355,7 +290,6 @@ var prototypeUpdateHelper = new UpdateHelper([
}
function defineSetters(obj, prop) {
storeProperties(obj);
if (obj.__properties.include(prop)) return;
obj.__properties.push(prop);
obj.__defineGetter__(prop, function() {
@ -369,7 +303,6 @@ var prototypeUpdateHelper = new UpdateHelper([
}
function checkProperties(hash) {
storeProperties(hash);
var current = Object.keys(hash);
if (current.length == hash.__properties.length)
return;
@ -380,12 +313,6 @@ var prototypeUpdateHelper = new UpdateHelper([
});
}
function storeProperties(h) {
if (typeof h.__properties === 'undefined')
h.__properties = __properties.clone();
return h;
}
Hash.prototype.set = Hash.prototype.set.wrap(function(proceed, property, value) {
defineSetters(this, property);
return proceed(property, value);
@ -408,7 +335,7 @@ var prototypeUpdateHelper = new UpdateHelper([
});
Hash.prototype.initialize = Hash.prototype.initialize.wrap(function(proceed, object) {
storeProperties(this);
this.__properties = __properties.clone();
for (var prop in object) defineSetters(this, prop);
proceed(object);
});

View File

@ -1,4 +1,4 @@
/* Update Helper (c) 2008-2009 Tobie Langel
/* Update Helper (c) 2008 Tobie Langel
*
* Requires Prototype >= 1.6.0
*
@ -54,12 +54,9 @@ var UpdateHelper = Class.create({
try {
throw new Error("stack");
} catch(e) {
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 ''; }
return (e.stack || '').match(this.Regexp).reject(function(path) {
return /(prototype|unittest|update_helper)\.js/.test(path);
}).join("\n");
}
},

37
lib/protodoc.rb Normal file
View File

@ -0,0 +1,37 @@
require 'erb'
class String
def lines
split $/
end
def strip_whitespace_at_line_ends
lines.map {|line| line.gsub(/\s+$/, '')} * $/
end
end
module Protodoc
module Environment
def include(*filenames)
filenames.map {|filename| Preprocessor.new(filename).to_s}.join("\n")
end
end
class Preprocessor
include Environment
def initialize(filename)
filename = File.join(filename.split('/'))
@filename = File.expand_path(filename)
@template = ERB.new(IO.read(@filename), nil, '%')
end
def to_s
@template.result(binding).strip_whitespace_at_line_ends
end
end
end
if __FILE__ == $0
print Protodoc::Preprocessor.new(ARGV.first)
end

7
src/HEADER Normal file
View File

@ -0,0 +1,7 @@
/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %>
* (c) 2005-2008 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
*
*--------------------------------------------------------------------------*/

View File

@ -1,116 +0,0 @@
/**
* == Ajax ==
*
* Prototype's APIs around the `XmlHttpRequest` object.
*
* The Prototype framework enables you to deal with Ajax calls in a manner that is
* both easy and compatible with all modern browsers.
*
* Actual requests are made by creating instances of [[Ajax.Request]].
*
* <h5>Request headers</h5>
*
* The following headers are sent with all Ajax requests (and can be
* overridden with the `requestHeaders` option described below):
*
* * `X-Requested-With` is set to `XMLHttpRequest`.
* * `X-Prototype-Version` is set to Prototype's current version (e.g.,
* `1.6.0.3`).
* * `Accept` is set to `text/javascript, text/html, application/xml,
* text/xml, * / *`
* * `Content-type` is automatically determined based on the `contentType`
* and `encoding` options.
*
* <h5>Ajax options</h5>
*
* All Ajax classes share a common set of _options_ and _callbacks_.
* Callbacks are called at various points in the life-cycle of a request, and
* always feature the same list of arguments.
*
* <h5>Common options</h5>
*
* * `asynchronous` ([[Boolean]]; default `true`): Determines whether
* `XMLHttpRequest` is used asynchronously or not. Synchronous usage is
* **strongly discouraged** &mdash; it halts all script execution for the
* duration of the request _and_ blocks the browser UI.
* * `contentType` ([[String]]; default `application/x-www-form-urlencoded`):
* The `Content-type` header for your request. Change this header if you
* want to send data in another format (like XML).
* * `encoding` ([[String]]; default `UTF-8`): The encoding for the contents
* of your request. It is best left as-is, but should weird encoding issues
* arise, you may have to tweak this.
* * `method` ([[String]]; default `post`): The HTTP method to use for the
* request. The other common possibility is `get`. Abiding by Rails
* conventions, Prototype also reacts to other HTTP verbs (such as `put` and
* `delete`) by submitting via `post` and adding a extra `_method` parameter
* with the originally-requested method.
* * `parameters` ([[String]]): The parameters for the request, which will be
* encoded into the URL for a `get` method, or into the request body for the
* other methods. This can be provided either as a URL-encoded string, a
* [[Hash]], or a plain [[Object]].
* * `postBody` ([[String]]): Specific contents for the request body on a
* `post` method. If it is not provided, the contents of the `parameters`
* option will be used instead.
* * `requestHeaders` ([[Object]]): A set of key-value pairs, with properties
* representing header names.
* * `evalJS` ([[Boolean]] | [[String]]; default `true`): Automatically `eval`s
* the content of [[Ajax.Response#responseText]] and populates
* [[Ajax.Response#responseJSON]] with it if the `Content-type` returned by
* the server is set to `application/json`. If the request doesn't obey
* same-origin policy, the content is sanitized before evaluation. If you
* need to force evalutation, pass `'force'`. To prevent it altogether, pass
* `false`.
* * `sanitizeJSON` ([[Boolean]]; default is `false` for same-origin requests,
* `true` otherwise): Sanitizes the contents of
* [[Ajax.Response#responseText]] before evaluating it.
*
* <h5>Common callbacks</h5>
*
* When used on individual instances, all callbacks (except `onException`) are
* invoked with two parameters: the `XMLHttpRequest` object and the result of
* evaluating the `X-JSON` response header, if any (can be `null`).
*
* For another way of describing their chronological order and which callbacks
* are mutually exclusive, see [[Ajax.Request]].
*
* * `onCreate`: Triggered when the [[Ajax.Request]] object is initialized.
* This is _after_ the parameters and the URL have been processed, but
* _before_ opening the connection via the XHR object.
* * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object
* is created.
* * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object
* is being setup, and its connection opened.
* * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object
* is setup, the connection is open, and it is ready to send its actual
* request.
* * `onInteractive` (*Not guaranteed*): Triggered whenever the requester
* receives a part of the response (but not the final part), should it
* be sent in several packets.
* * `onSuccess`: Invoked when a request completes and its status code is
* `undefined` or belongs in the `2xy` family. This is skipped if a
* code-specific callback is defined (e.g., `on200`), and happens _before_
* `onComplete`.
* * `onFailure`: Invoked when a request completes and its status code exists
* but _is not_ in the `2xy` family. This is skipped if a code-specific
* callback is defined (e.g. `on403`), and happens _before_ `onComplete`.
* * `onXYZ` (_with `XYZ` representing any HTTP status code_): Invoked just
* after the response is complete _if_ the status code is the exact code
* used in the callback name. _Prevents_ execution of `onSuccess` and
* `onFailure`. Happens _before_ `onComplete`.
* * `onException`: Triggered whenever an XHR error arises. Has a custom
* signature: the first argument is the requester (i.e. an [[Ajax.Request]]
* instance), and the second is the exception object.
* * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after
* the request completes, status-specific callbacks are called, and possible
* automatic behaviors are processed. Guaranteed to run regardless of what
* happened during the request.
*
**/
//= require "ajax/ajax"
//= require "ajax/responders"
//= require "ajax/base"
//= require "ajax/request"
//= require "ajax/response"
//= require "ajax/updater"
//= require "ajax/periodical_updater"

View File

@ -1,7 +1,3 @@
/** section: Ajax
* Ajax
**/
var Ajax = {
getTransport: function() {
return Try.these(
@ -10,12 +6,6 @@ var Ajax = {
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
/**
* Ajax.activeRequestCount -> Number
*
* Represents the number of active XHR requests triggered through
* [[Ajax.Request]], [[Ajax.Updater]], or [[Ajax.PeriodicalUpdater]].
**/
activeRequestCount: 0
};

View File

@ -1,4 +1,3 @@
// Abstract class; does not need documentation.
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
@ -11,10 +10,10 @@ Ajax.Base = Class.create({
evalJS: true
};
Object.extend(this.options, options || { });
this.options.method = this.options.method.toLowerCase();
if (Object.isString(this.options.parameters))
if (Object.isString(this.options.parameters))
this.options.parameters = this.options.parameters.toQueryParams();
else if (Object.isHash(this.options.parameters))
this.options.parameters = this.options.parameters.toObject();

View File

@ -1,69 +1,11 @@
/** section: Ajax
* class Ajax.PeriodicalUpdater
*
* Periodically performs an Ajax request and updates a container's contents
* based on the response text.
*
* `Ajax.PeriodicalUpdater` behaves like [[Ajax.Updater]], but performs the
* update at a prescribed interval, rather than only once. (Note that it is
* _not_ a subclass of `Ajax.Updater`; it's a wrapper around it.)
*
* This class addresses the common need of periodical update, as required by
* all sorts of "polling" mechanisms (e.g., an online chatroom or an online
* mail client).
*
* The basic idea is to run a regular [[Ajax.Updater]] at regular intervals,
* keeping track of the response text so it can (optionally) react to
* receiving the exact same response consecutively.
*
* <h5>Additional options</h5>
*
* `Ajax.PeriodicalUpdater` features all the common options and callbacks
* described in the [[Ajax section]] &mdash; _plus_ those added by `Ajax.Updater`.
*
* It also provides two new options:
*
* * `frequency` ([[Number]]; default is `2`): How long, in seconds, to wait
* between the end of one request and the beginning of the next.
* * `decay` ([[Number]]; default is `1`): The rate at which the `frequency`
* grows when the response received is _exactly_ the same as the previous.
* The default of `1` means `frequency` will never grow; override the
* default if a stale response implies it's worthwhile to poll less often.
* If `decay` is set to `2`, for instance, `frequency` will double
* (2 seconds, 4 seconds, 8 seconds...) each consecutive time the result
* is the same; when the result is different once again, `frequency` will
* revert to its original value.
*
* <h5>Disabling and re-enabling a <code>PeriodicalUpdater</code></h5>
*
* You can hit the brakes on a running `PeriodicalUpdater` by calling
* [[Ajax.PeriodicalUpdater#stop]]. If you wish to re-enable it later, call
* [[Ajax.PeriodicalUpdater#start]].
*
**/
Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
/**
* new Ajax.PeriodicalUpdater(container, url[, options])
* - container (String | Element): The DOM element whose contents to update
* as a result of the Ajax request. Can be a DOM node or a string that
* identifies a node's ID.
* - url (String): The URL to fetch. When the _same-origin_ policy is in
* effect (as it is in most cases), `url` **must** be a relative URL or an
* absolute URL that starts with a slash (i.e., it must not begin with
* `http`).
* - options (Object): Configuration for the request. See the
* [[Ajax section]] for more information.
*
* Creates a new `Ajax.PeriodicalUpdater`.
**/
initialize: function($super, container, url, options) {
$super(options);
this.onComplete = this.options.onComplete;
this.frequency = (this.options.frequency || 2);
this.decay = (this.options.decay || 1);
this.updater = { };
this.container = container;
this.url = url;
@ -71,24 +13,11 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
this.start();
},
/**
* Ajax.PeriodicalUpdater#start() -> undefined
*
* Starts the periodical updater (if it had previously been stopped with
* [[Ajax.PeriodicalUpdater#stop]]).
**/
start: function() {
this.options.onComplete = this.updateComplete.bind(this);
this.onTimerEvent();
},
/**
* Ajax.PeriodicalUpdater#stop() -> undefined
*
* Stops the periodical updater.
*
* Also calls the `onComplete` callback, if one has been defined.
**/
stop: function() {
this.updater.options.onComplete = undefined;
clearTimeout(this.timer);
@ -97,7 +26,7 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
updateComplete: function(response) {
if (this.options.decay) {
this.decay = (response.responseText == this.lastText ?
this.decay = (response.responseText == this.lastText ?
this.decay * this.options.decay : 1);
this.lastText = response.responseText;

View File

@ -1,92 +1,6 @@
/** section: Ajax
* class Ajax.Request
*
* Initiates and processes an Ajax request.
*
* `Ajax.Request` is a general-purpose class for making HTTP requests.
*
* <h5>Automatic JavaScript response evaluation</h5>
*
* If an Ajax request follows the _same-origin policy_ **and** its response
* has a JavaScript-related `Content-type`, the content of the `responseText`
* property will automatically be passed to `eval`.
*
* In other words: you don't even need to provide a callback to leverage
* pure-JavaScript Ajax responses. This is the convention that drives Rails's
* RJS.
*
* The list of JavaScript-related MIME-types handled by Prototype is:
*
* * `application/ecmascript`
* * `application/javascript`
* * `application/x-ecmascript`
* * `application/x-javascript`
* * `text/ecmascript`
* * `text/javascript`
* * `text/x-ecmascript`
* * `text/x-javascript`
*
* The MIME-type string is examined in a case-insensitive manner.
*
* <h5>Methods you may find useful</h5>
*
* Instances of the `Request` object provide several methods that can come in
* handy in your callback functions, especially once the request is complete.
*
* <h5>Is the response a successful one?</h5>
*
* The [[Ajax.Request#success]] method examines the XHR object's `status`
* property and follows general HTTP guidelines: unknown status is deemed
* successful, as is the whole `2xy` status code family. It's a generally
* better way of testing your response than the usual
* `200 == transport.status`.
*
* <h5>Getting HTTP response headers</h5>
*
* While you can obtain response headers from the XHR object using its
* `getResponseHeader` method, this makes for verbose code, and several
* implementations raise an exception when the header is not found. To make
* this easier, you can use the [[Ajax.Response#getHeader]] method, which
* delegates to the longer version and returns `null` if an exception occurs:
*
* new Ajax.Request('/your/url', {
* onSuccess: function(response) {
* // Note how we brace against null values
* if ((response.getHeader('Server') || '').match(/Apache/))
* ++gApacheCount;
* // Remainder of the code
* }
* });
*
* <h5>Evaluating JSON headers</h5>
*
* Some backends will return JSON not as response text, but in the `X-JSON`
* header. In this case, you don't even need to evaluate the returned JSON
* yourself, as Prototype automatically does so. It passes the result as the
* `headerJSON` property of the [[Ajax.Response]] object. Note that if there
* is no such header &mdash; or its contents are invalid &mdash; `headerJSON` will be set
* to `null`.
*
* new Ajax.Request('/your/url', {
* onSuccess: function(transport) {
* transport.headerJSON
* }
* });
**/
Ajax.Request = Class.create(Ajax.Base, {
_complete: false,
/**
* new Ajax.Request(url[, options])
* - url (String): The URL to fetch. When the _same-origin_ policy is in
* effect (as it is in most cases), `url` **must** be a relative URL or an
* absolute URL that starts with a slash (i.e., it must not begin with
* `http`).
* - options (Object): Configuration for the request. See the
* [[Ajax section]] for more information.
*
* Creates a new `Ajax.Request`.
**/
initialize: function($super, url, options) {
$super(options);
this.transport = Ajax.getTransport();
@ -103,7 +17,7 @@ Ajax.Request = Class.create(Ajax.Base, {
params['_method'] = this.method;
this.method = 'post';
}
this.parameters = params;
if (params = Object.toQueryString(params)) {
@ -113,17 +27,17 @@ Ajax.Request = Class.create(Ajax.Base, {
else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
params += '&_=';
}
try {
var response = new Ajax.Response(this);
if (this.options.onCreate) this.options.onCreate(response);
Ajax.Responders.dispatch('onCreate', this, response);
this.transport.open(this.method.toUpperCase(), this.url,
this.transport.open(this.method.toUpperCase(), this.url,
this.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
@ -133,7 +47,7 @@ Ajax.Request = Class.create(Ajax.Base, {
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
@ -145,7 +59,7 @@ Ajax.Request = Class.create(Ajax.Base, {
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
@ -156,47 +70,42 @@ Ajax.Request = Class.create(Ajax.Base, {
if (this.method == 'post') {
headers['Content-type'] = this.options.contentType +
(this.options.encoding ? '; charset=' + this.options.encoding : '');
/* Force "Connection: close" for older Mozilla browsers to work
* around a bug where XMLHttpRequest sends an incorrect
* Content-length header. See Mozilla Bugzilla #246651.
* Content-length header. See Mozilla Bugzilla #246651.
*/
if (this.transport.overrideMimeType &&
(navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
headers['Connection'] = 'close';
}
// user-defined headers
if (typeof this.options.requestHeaders == 'object') {
var extras = this.options.requestHeaders;
if (Object.isFunction(extras.push))
for (var i = 0, length = extras.length; i < length; i += 2)
for (var i = 0, length = extras.length; i < length; i += 2)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
/**
* Ajax.Request#success() -> Boolean
*
* Tests whether the request was successful.
**/
success: function() {
var status = this.getStatus();
return !status || (status >= 200 && status < 300);
},
getStatus: function() {
try {
return this.transport.status || 0;
} catch (e) { return 0 }
} catch (e) { return 0 }
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
@ -209,10 +118,10 @@ Ajax.Request = Class.create(Ajax.Base, {
} catch (e) {
this.dispatchException(e);
}
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
|| (this.options.evalJS && this.isSameOrigin() && contentType
|| (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
@ -223,13 +132,13 @@ Ajax.Request = Class.create(Ajax.Base, {
} catch (e) {
this.dispatchException(e);
}
if (state == 'Complete') {
// avoid memory leak in MSIE: clean up
this.transport.onreadystatechange = Prototype.emptyFunction;
}
},
isSameOrigin: function() {
var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
@ -238,21 +147,13 @@ Ajax.Request = Class.create(Ajax.Base, {
port: location.port ? ':' + location.port : ''
}));
},
/**
* Ajax.Request#getHeader(name) -> String | null
* - name (String): The name of an HTTP header that may have been part of
* the response.
*
* Returns the value of the given response header, or `null` if that header
* was not found.
**/
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name) || null;
} catch (e) { return null; }
} catch (e) { return null }
},
evalResponse: function() {
try {
return eval((this.transport.responseText || '').unfilterJSON());
@ -267,5 +168,5 @@ Ajax.Request = Class.create(Ajax.Base, {
}
});
Ajax.Request.Events =
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

View File

@ -1,107 +1,19 @@
/** section: Ajax
* Ajax.Responders
* includes Enumerable
*
* A repository of global listeners notified about every step of
* Prototype-based Ajax requests.
*
* Sometimes, you need to provide generic behaviors over all Ajax operations
* happening on the page (through [[Ajax.Request]], [[Ajax.Updater]] or
* [[Ajax.PeriodicalUpdater]]).
*
* For instance, you might want to automatically show an indicator when an
* Ajax request is ongoing, and hide it when none are. You may well want to
* factor out exception handling as well, logging those somewhere on the page
* in a custom fashion. The possibilities are myriad.
*
* To achieve this, Prototype provides `Ajax.Responders`, which lets you
* register (and, if you wish, unregister later) _responders_, which are
* objects with specially-named methods. These names come from a set of
* general callbacks corresponding to different points in time (or outcomes)
* of an Ajax request's life cycle.
*
* For instance, Prototype automatically registers a responder that maintains
* a nifty variable: [[Ajax.activeRequestCount]]. This represents, at a given
* time, the number of currently active Ajax requests &mdash; by monitoring their
* `onCreate` and `onComplete` events. The code for this is fairly simple:
*
* Ajax.Responders.register({
* onCreate: function() {
* Ajax.activeRequestCount++;
* },
* onComplete: function() {
* Ajax.activeRequestCount--;
* }
* });
*
* <h5>Responder callbacks</h5>
*
* The callbacks for responders are similar to the callbacks described in
* the [[Ajax section]], but take a different signature. They're invoked with
* three parameters: the requester object (i.e., the corresponding "instance"
* of [[Ajax.Request]]), the `XMLHttpRequest` object, and the result of
* evaluating the `X-JSON` response header, if any (can be `null`). They also
* execute in the context of the responder, bound to the `this` reference.
*
* * `onCreate`: Triggered whenever a requester object from the `Ajax`
* namespace is created, after its parameters are adjusted and before its
* XHR connection is opened. This takes *two* arguments: the requester
* object and the underlying XHR object.
* * `onUninitialized` (*Not guaranteed*): Invoked just after the XHR object
* is created.
* * `onLoading` (*Not guaranteed*): Triggered when the underlying XHR object
* is being setup, and its connection opened.
* * `onLoaded` (*Not guaranteed*): Triggered once the underlying XHR object
* is setup, the connection is open, and it is ready to send its actual
* request.
* * `onInteractive` (*Not guaranteed*): Triggered whenever the requester
* receives a part of the response (but not the final part), should it
* be sent in several packets.
* * `onException`: Triggered whenever an XHR error arises. Has a custom
* signature: the first argument is the requester (i.e. an [[Ajax.Request]]
* instance), and the second is the exception object.
* * `onComplete`: Triggered at the _very end_ of a request's life-cycle, after
* the request completes, status-specific callbacks are called, and possible
* automatic behaviors are processed. Guaranteed to run regardless of what
* happened during the request.
*
**/
Ajax.Responders = {
responders: [],
_each: function(iterator) {
this.responders._each(iterator);
},
/**
* Ajax.Responders.register(responder) -> undefined
* - responder (Object): A list of functions with keys corresponding to the
* names of possible callbacks.
*
* Add a group of responders to all Ajax requests.
**/
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
/**
* Ajax.Responders.unregister(responder) -> undefined
* - responder (Object): A list of functions with keys corresponding to the
* names of possible callbacks.
*
* Remove a previously-added group of responders.
*
* As always, unregistering something requires you to use the very same
* object you used at registration. If you plan to use `unregister`, be sure
* to assign your responder to a _variable_ before passing it into
* [[Ajax.Responders#register]] &mdash; don't pass it an object literal.
**/
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (Object.isFunction(responder[callback])) {
@ -116,6 +28,6 @@ Ajax.Responders = {
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate: function() { Ajax.activeRequestCount++ },
onCreate: function() { Ajax.activeRequestCount++ },
onComplete: function() { Ajax.activeRequestCount-- }
});

View File

@ -1,147 +1,50 @@
/** section: Ajax
* class Ajax.Response
*
* A wrapper class around `XmlHttpRequest` for dealing with HTTP responses
* of Ajax requests.
*
* An instance of `Ajax.Response` is passed as the first argument of all Ajax
* requests' callbacks. You _will not_ need to create instances of
* `Ajax.Response` yourself.
**/
/**
* Ajax.Response#readyState -> Number
*
* The request's current state.
*
* `0` corresponds to `"Uninitialized"`, `1` to `"Loading"`, `2` to
* `"Loaded"`, `3` to `"Interactive"`, and `4` to `"Complete"`.
**/
/**
* Ajax.Response#responseText -> String
*
* The text body of the response.
**/
/**
* Ajax.Response#responseXML -> document | null
*
* The XML body of the response if the `Content-type` of the request is set
* to `application/xml`; `null` otherwise.
**/
/**
* Ajax.Response#responseJSON -> Object | Array | null
*
* The JSON body of the response if the `Content-type` of the request is set
* to `application/json`; `null` otherwise.
**/
/**
* Ajax.Response#headerJSON -> Object | Array | null
*
* Auto-evaluated content of the `X-JSON` header if present; `null` otherwise.
**/
/**
* Ajax.Response#request -> Ajax.Request | Ajax.Updater
*
* The request object itself (an instance of [[Ajax.Request]] or
* [[Ajax.Updater]]).
**/
/**
* Ajax.Response#transport -> XmlHttpRequest
*
* The native `XmlHttpRequest` object itself.
**/
Ajax.Response = Class.create({
// Don't document the constructor; should never be manually instantiated.
initialize: function(request){
this.request = request;
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();
}
},
/**
* Ajax.Response#status -> Number
*
* The HTTP status code sent by the server.
**/
status: 0,
/**
* Ajax.Response#statusText -> String
*
* The HTTP status text sent by the server.
**/
statusText: '',
getStatus: Ajax.Request.prototype.getStatus,
getStatusText: function() {
try {
return this.transport.statusText || '';
} catch (e) { return '' }
},
/**
* Ajax.Response#getHeader(name) -> String | null
*
* See [[Ajax.Request#getHeader]].
**/
getHeader: Ajax.Request.prototype.getHeader,
/**
* Ajax.Response#getAllHeaders() -> String | null
*
* Returns a string containing all headers separated by line breaks. _Does
* not__ throw errors if no headers are present the way its native
* counterpart does.
**/
getAllHeaders: function() {
try {
return this.getAllResponseHeaders();
} catch (e) { return null }
} catch (e) { return null }
},
/**
* 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
* the same name.
**/
getResponseHeader: function(name) {
return this.transport.getResponseHeader(name);
},
/**
* 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
* `XmlHttpRequest` method of the same name.
**/
getAllResponseHeaders: function() {
return this.transport.getAllResponseHeaders();
},
_getHeaderJSON: function() {
var json = this.getHeader('X-JSON');
if (!json) return null;
@ -153,11 +56,11 @@ Ajax.Response = Class.create({
this.request.dispatchException(e);
}
},
_getResponseJSON: function() {
var options = this.request.options;
if (!options.evalJSON || (options.evalJSON != 'force' &&
!(this.getHeader('Content-type') || '').include('application/json')) ||
if (!options.evalJSON || (options.evalJSON != 'force' &&
!(this.getHeader('Content-type') || '').include('application/json')) ||
this.responseText.blank())
return null;
try {

View File

@ -1,96 +1,4 @@
/** section: Ajax
* class Ajax.Updater < Ajax.Request
*
* A class that performs an Ajax request and updates a container's contents
* with the contents of the response.
*
* `Ajax.Updater` is a subclass of [[Ajax.Request]] built for a common
* use-case.
*
* <h5>Example</h5>
*
* new Ajax.Updater('items', '/items', {
* parameters: { text: $F('text') }
* });
*
* This example will make a request to the URL `/items` (with the given
* parameters); it will then replace the contents of the element with the ID
* of `items` with whatever response it receives.
*
* <h5>Callbacks</h5>
*
* `Ajax.Updater` supports all the callbacks listed in the [[Ajax section]].
* Note that the `onComplete` callback will be invoked **after** the element
* is updated.
*
* <h5>Additional options</h5>
*
* `Ajax.Updater` has some options of its own apart from the common options
* described in the [[Ajax section]]:
*
* * `evalScripts` ([[Boolean]]; defaults to `false`): Whether `<script>`
* elements in the response text should be evaluated.
* * `insertion` ([[String]]): By default, `Element.update` is used, meaning
* the contents of the response will replace the entire contents of the
* container. You may _instead_ insert the response text without disrupting
* existing contents. The `insertion` option takes one of four strings &mdash;
* `top`, `bottom`, `before`, or `after` &mdash; and _inserts_ the contents of the
* response in the manner described by [[Element#insert]].
*
* <h5>More About `evalScripts`</h5>
*
* If you use `evalScripts: true`, any _inline_ `<script>` block will be evaluated.
* This **does not** mean it will be evaluated in the global scope; it won't, and that
* has important ramifications for your `var` and `function` statements. Also note
* that only inline `<script>` blocks are supported; external scripts are ignored.
* See [[String#evalScripts]] for the details.
*
* <h5>Single container, or success/failure split?</h5>
*
* The examples above all assume you're going to update the same container
* whether your request succeeds or fails. Instead, you may want to update
* _only_ for successful requests, or update a _different container_ on failed
* requests.
*
* To achieve this, you can pass an object instead of a DOM element for the
* `container` parameter. This object _must_ have a `success` property whose
* value identifies the container to be updated on successful requests.
*
* If you also provide it with a `failure` property, its value will be used as
* the container for failed requests.
*
* In the following code, only successful requests get an update:
*
* new Ajax.Updater({ success: 'items' }, '/items', {
* parameters: { text: $F('text') },
* insertion: 'bottom'
* });
*
* This next example assumes failed requests will deliver an error message as
* response text &mdash; one that should be shown to the user in another area:
*
* new Ajax.Updater({ success: 'items', failure: 'notice' }, '/items',
* parameters: { text: $F('text') },
* insertion: 'bottom'
* });
*
**/
Ajax.Updater = Class.create(Ajax.Request, {
/**
* new Ajax.Updater(container, url[, options])
* - container (String | Element): The DOM element whose contents to update
* as a result of the Ajax request. Can be a DOM node or a string that
* identifies a node's ID.
* - url (String): The URL to fetch. When the _same-origin_ policy is in
* effect (as it is in most cases), `url` **must** be a relative URL or an
* absolute URL that starts with a slash (i.e., it must not begin with
* `http`).
* - options (Object): Configuration for the request. See the
* [[Ajax section]] for more information.
*
* Creates a new `Ajax.Updater`.
**/
initialize: function($super, container, url, options) {
this.container = {
success: (container.success || container),
@ -108,11 +16,11 @@ Ajax.Updater = Class.create(Ajax.Request, {
},
updateContent: function(responseText) {
var receiver = this.container[this.success() ? 'success' : 'failure'],
var receiver = this.container[this.success() ? 'success' : 'failure'],
options = this.options;
if (!options.evalScripts) responseText = responseText.stripScripts();
if (receiver = $(receiver)) {
if (options.insertion) {
if (Object.isString(options.insertion)) {
@ -120,7 +28,7 @@ Ajax.Updater = Class.create(Ajax.Request, {
receiver.insert(insertion);
}
else options.insertion(receiver, responseText);
}
}
else receiver.update(responseText);
}
}

View File

@ -1 +0,0 @@
PROTOTYPE_VERSION: 1.6.1

View File

@ -10,15 +10,15 @@ var Insertion = {
Before: function(element, content) {
return Element.insert(element, {before:content});
},
Top: function(element, content) {
return Element.insert(element, {top:content});
},
Bottom: function(element, content) {
return Element.insert(element, {bottom:content});
},
After: function(element, content) {
return Element.insert(element, {after:content});
}
@ -32,21 +32,21 @@ var Position = {
// set to true if needed, warning: firefox performance problems
// NOT neeeded for page scrolling, only if draggable contained in
// scrollable elements
includeScrollOffsets: false,
includeScrollOffsets: false,
// must be called before calling withinIncludingScrolloffset, every time the
// page is scrolled
prepare: function() {
this.deltaX = window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
this.deltaX = window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0;
this.deltaY = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
this.deltaY = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
},
// caches x/y coordinate pair to use with overlap
within: function(element, x, y) {
if (this.includeScrollOffsets)
@ -57,7 +57,7 @@ var Position = {
return (y >= this.offset[1] &&
y < this.offset[1] + element.offsetHeight &&
x >= this.offset[0] &&
x >= this.offset[0] &&
x < this.offset[0] + element.offsetWidth);
},
@ -70,18 +70,18 @@ var Position = {
return (this.ycomp >= this.offset[1] &&
this.ycomp < this.offset[1] + element.offsetHeight &&
this.xcomp >= this.offset[0] &&
this.xcomp >= this.offset[0] &&
this.xcomp < this.offset[0] + element.offsetWidth);
},
// within must be called directly before
overlap: function(mode, element) {
if (!mode) return 0;
if (mode == 'vertical')
return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
overlap: function(mode, element) {
if (!mode) return 0;
if (mode == 'vertical')
return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
element.offsetHeight;
if (mode == 'horizontal')
return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
element.offsetWidth;
},
@ -161,21 +161,21 @@ Element.ClassNames.prototype = {
return name.length > 0;
})._each(iterator);
},
set: function(className) {
this.element.className = className;
},
add: function(classNameToAdd) {
if (this.include(classNameToAdd)) return;
this.set($A(this).concat(classNameToAdd).join(' '));
},
remove: function(classNameToRemove) {
if (!this.include(classNameToRemove)) return;
this.set($A(this).without(classNameToRemove).join(' '));
},
toString: function() {
return $A(this).join(' ');
}
@ -184,95 +184,3 @@ 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

@ -1,35 +0,0 @@
/**
* == DOM ==
* Extensions to DOM elements, plus other utilities for DOM traversal
* and modification.
*
* Prototype's DOM extensions represent a large portion of where you'll spend
* your time. Prototype adds many convenience methods to elements returned by
* the [[$]] function. For instance, you can write
*
* $('comments').addClassName('active').show();
*
* to get the element with the ID of `comments`, add a class name to it, and
* show it (if it was previously hidden).
*
* In other words, Prototype adds "instance" methods to DOM nodes. This is
* made possible by direct extension of the backing DOM objects (in browsers
* that support it) and by manual extension of individual nodes (in browsers
* that do not).
*
**/
/** section: DOM
* Prototype
*
* The Prototype namespace.
*
**/
//= require "dom/dom"
//= require <selector_engine>
//= require "dom/selector"
//= require "dom/form"
//= require "dom/event"
Element.addMethods();

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,4 @@
(function() {
/** section: DOM
* class Event
*
* The namespace for Prototype's event system.
*
* <h5>Events: a fine mess</h5>
*
* Event management is one of the really sore spots of cross-browser
* scripting.
*
* True, the prevalent issue is: everybody does it the W3C way, and MSIE
* does it another way altogether. But there are quite a few subtler,
* sneakier issues here and there waiting to bite your ankle &mdash; such as the
* `keypress`/`keydown` issue with KHTML-based browsers (Konqueror and
* Safari). Also, MSIE has a tendency to leak memory when it comes to
* discarding event handlers.
*
* <h5>Prototype to the rescue</h5>
*
* Of course, Prototype smooths it over so well you'll forget these
* troubles even exist. Enter the `Event` namespace. It is replete with
* methods that help to normalize the information reported by events across
* browsers.
*
* `Event` also provides a standardized list of key codes you can use with
* 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,
* you'll also get a lot of mileage out of [[Event.fire]].
**/
var Event = {
KEY_BACKSPACE: 8,
KEY_TAB: 9,
@ -53,10 +18,6 @@
cache: {}
};
var docEl = document.documentElement;
var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
&& 'onmouseleave' in docEl;
var _isButton;
if (Prototype.Browser.IE) {
// IE doesn't map left/right/middle the same way.
@ -80,44 +41,10 @@
};
}
/**
* Event.isLeftClick(@event) -> Boolean
*
* Determines whether a button-related mouse event involved the left
* mouse button.
*
* Keep in mind that the "left" mouse button is actually the "primary" mouse
* button. When a mouse is in left-handed mode, the browser will report
* clicks of the _right_ button as "left-clicks."
**/
function isLeftClick(event) { return _isButton(event, 0) }
/**
* Event.isMiddleClick(@event) -> Boolean
*
* Determines whether a button-related mouse event involved the middle
* mouse button.
**/
function isMiddleClick(event) { return _isButton(event, 1) }
/**
* Event.isRightClick(@event) -> Boolean
*
* Determines whether a button-related mouse event involved the right
* mouse button.
*
* Keep in mind that the "left" mouse button is actually the "secondary"
* mouse button. When a mouse is in left-handed mode, the browser will
* report clicks of the _left_ button as "left-clicks."
**/
function isRightClick(event) { return _isButton(event, 2) }
/** deprecated
* Event.element(@event) -> Element
*
* Returns the DOM element on which the event occurred. This method
* is deprecated, use [[Event.findElement findElement]] instead.
**/
function element(event) {
event = Event.extend(event);
@ -142,84 +69,29 @@
return Element.extend(node);
}
/**
* Event.findElement(@event, expression) -> Element
*
* Returns the first DOM element that matches a given CSS selector &mdash;
* starting with the element on which the event occurred, then moving up
* its ancestor chain.
**/
function findElement(event, expression) {
var element = Event.element(event);
if (!expression) return element;
while (element) {
if (Prototype.Selector.match(element, expression)) {
return Element.extend(element);
}
element = element.parentNode;
}
var elements = [element].concat(element.ancestors());
return Selector.findElement(elements, expression, 0);
}
/**
* Event.pointer(@event) -> Object
*
* Returns the absolute position of the pointer for a mouse event.
*
* Returns an object in the form `{ x: Number, y: Number}`.
*
* Note that this position is absolute on the _page_, not on the
* _viewport_.
**/
function pointer(event) {
return { x: pointerX(event), y: pointerY(event) };
}
/**
* Event.pointerX(@event) -> Number
*
* Returns the absolute horizontal position of the pointer for a mouse
* event.
*
* Note that this position is absolute on the _page_, not on the
* _viewport_.
**/
function pointerX(event) {
var docElement = document.documentElement,
body = document.body || { scrollLeft: 0 };
return event.pageX || (event.clientX +
(docElement.scrollLeft || body.scrollLeft) -
(docElement.clientLeft || 0));
body = document.body || { scrollLeft: 0, scrollTop: 0 };
return {
x: event.pageX || (event.clientX +
(docElement.scrollLeft || body.scrollLeft) -
(docElement.clientLeft || 0)),
y: event.pageY || (event.clientY +
(docElement.scrollTop || body.scrollTop) -
(docElement.clientTop || 0))
};
}
/**
* Event.pointerY(@event) -> Number
*
* Returns the absolute vertical position of the pointer for a mouse
* event.
*
* Note that this position is absolute on the _page_, not on the
* _viewport_.
**/
function pointerY(event) {
var docElement = document.documentElement,
body = document.body || { scrollTop: 0 };
function pointerX(event) { return Event.pointer(event).x }
function pointerY(event) { return Event.pointer(event).y }
return event.pageY || (event.clientY +
(docElement.scrollTop || body.scrollTop) -
(docElement.clientTop || 0));
}
/**
* Event.stop(@event) -> undefined
*
* Stops the event's propagation and prevents its eventual default action
* from being triggered.
*
* Stopping an event also sets a `stopped` property on that event for
* future inspection.
**/
function stop(event) {
Event.extend(event);
event.preventDefault();
@ -270,22 +142,18 @@
});
// IE's method for extending events.
Event.extend = function(event, element) {
Event.extend = function(event) {
if (!event) return false;
if (event._extendedByPrototype) return event;
event._extendedByPrototype = Prototype.emptyFunction;
var pointer = Event.pointer(event);
// The optional `element` argument gives us a fallback value for the
// `target` property in case IE doesn't give us through `srcElement`.
Object.extend(event, {
target: event.srcElement || element,
target: event.srcElement,
relatedTarget: _relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
return Object.extend(event, methods);
};
} else {
@ -294,85 +162,67 @@
Event.extend = Prototype.K;
}
function _getEventID(element) {
if (element._prototypeEventID) return element._prototypeEventID[0];
return element._prototypeEventID = [++arguments.callee.id];
}
_getEventID.id = 1;
function _getDOMEventName(eventName) {
if (eventName && eventName.include(':')) return 'dataavailable';
return eventName;
}
function _getCacheForID(id) {
return Event.cache[id] = Event.cache[id] || { };
}
function _getRespondersForEvent(id, eventName) {
var c = _getCacheForID(id);
return c[eventName] = c[eventName] || [];
}
function _createResponder(element, eventName, handler) {
// We don't set a default on the call to Element#retrieve so that we can
// handle the element's "virgin" state.
var registry = Element.retrieve(element, 'prototype_event_registry');
if (Object.isUndefined(registry)) {
// First time we've handled this element. Put it into the cache.
CACHE.push(element);
registry = Element.retrieve(element, 'prototype_event_registry', $H());
}
var respondersForEvent = registry.get(eventName);
if (Object.isUndefined(respondersForEvent)) {
respondersForEvent = [];
registry.set(eventName, respondersForEvent);
}
var id = _getEventID(element), r = _getRespondersForEvent(id, eventName);
// Work around the issue that permits a handler to be attached more than
// once to the same element & event type.
if (respondersForEvent.pluck('handler').include(handler)) return false;
if (r.pluck('handler').include(handler)) return false;
var responder;
if (eventName.include(":")) {
// Custom event.
responder = function(event) {
// If it's not a custom event, ignore it.
if (Object.isUndefined(event.eventName))
return false;
var responder = function(event) {
if (!Event || !Event.extend ||
// If it's a custom event, but not the _correct_ custom event, ignore it.
(!Object.isUndefined(event.eventName) && event.eventName !== eventName))
return false;
// If it's a custom event, but not the _correct_ custom event, ignore it.
if (event.eventName !== eventName)
return false;
Event.extend(event, element);
handler.call(element, event);
};
} else {
// Non-custom event.
if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
(eventName === "mouseenter" || eventName === "mouseleave")) {
// If we're dealing with mouseenter or mouseleave in a non-IE browser,
// we create a custom responder that mimics their behavior within
// mouseover and mouseout.
if (eventName === "mouseenter" || eventName === "mouseleave") {
responder = function(event) {
Event.extend(event, element);
var parent = event.relatedTarget;
while (parent && parent !== element) {
try { parent = parent.parentNode; }
catch(e) { parent = element; }
}
if (parent === element) return;
handler.call(element, event);
};
}
} else {
responder = function(event) {
Event.extend(event, element);
handler.call(element, event);
};
}
}
Event.extend(event);
handler.call(element, event);
};
responder.handler = handler;
respondersForEvent.push(responder);
r.push(responder);
return responder;
}
function _destroyCache() {
for (var i = 0, length = CACHE.length; i < length; i++) {
Event.stopObserving(CACHE[i]);
CACHE[i] = null;
}
function _findResponder(id, eventName, handler) {
var r = _getRespondersForEvent(id, eventName);
return r.find(function(responder) {
return responder.handler === handler;
});
}
var CACHE = [];
function _destroyResponder(id, eventName, handler) {
var c = _getCacheForID(id);
if (Object.isUndefined(c[eventName])) return false;
c[eventName] = c[eventName].without(_findResponder(id, eventName, handler));
}
function _destroyCache() {
for (var id in Event.cache) {
for (var eventName in Event.cache[id])
Event.cache[id][eventName] = null;
}
}
// Internet Explorer needs to remove event handlers on page unload
// in order to avoid memory leaks.
@ -386,312 +236,56 @@
window.addEventListener('unload', Prototype.emptyFunction, false);
var _getDOMEventName = Prototype.K,
translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
_getDOMEventName = function(eventName) {
return (translations[eventName] || eventName);
};
}
/**
* Event.observe(element, eventName, handler) -> Element
* - element (Element | String): The DOM element to observe, or its ID.
* - eventName (String): The name of the event, in all lower case, without the "on"
* prefix&nbsp;&mdash; e.g., "click" (not "onclick").
* - handler (Function): The function to call when the event occurs.
*
* Registers an event handler on a DOM element. Aliased as [[Element#observe]].
*
* `Event.observe` smooths out a variety of differences between browsers and provides
* some handy additional features as well. Key features in brief:
* * Several handlers can be registered for the same event on the same element.
* * Prototype figures out whether to use `addEventListener` (W3C standard) or
* `attachEvent` (MSIE); you don't have to worry about it.
* * The handler is passed an _extended_ [[Event]] object (even on MSIE).
* * The handler's context (`this` value) is set to the extended element being observed
* (even if the event actually occurred on a descendent element and bubbled up).
* * Prototype handles cleaning up the handler when leaving the page (important for MSIE memory
* leak prevention).
* * `observe` makes it possible to stop observing the event easily via [[Event.stopObserving]].
* * Adds support for `mouseenter` / `mouseleave` in all browsers.
*
* Although you can use `Event.observe` directly and there are times when that's the most
* convenient or direct way, it's more common to use its alias [[Element#observe]]. These two
* statements have the same effect:
*
* Event.observe('foo', 'click', myHandler);
* $('foo').observe('click', myHandler);
*
* The examples in this documentation use the [[Element#observe]] form.
*
* <h5>The Handler</h5>
*
* Signature:
*
* function handler(event) {
* // `this` = the element being observed
* }
*
* So for example, this will turn the background of the element 'foo' blue when it's clicked:
*
* $('foo').observe('click', function(event) {
* this.setStyle({backgroundColor: 'blue'});
* });
*
* Note that we used `this` to refer to the element, and that we received the `event` object
* as a parameter (even on MSIE).
*
* <h5>It's All About Timing</h5>
*
* One of the most common errors trying to observe events is trying to do it before the element
* exists in the DOM. Don't try to observe elements until after the
* [[document.observe dom:loaded]] event or `window` `load` event has been fired.
*
* <h5>Preventing the Default Event Action and Bubbling</h5>
*
* If we want to stop the event (e.g., prevent its default action and stop it bubbling), we can
* do so with the extended event object's [[Event#stop]] method:
*
* $('foo').observe('click', function(event) {
* event.stop();
* });
*
* <h5>Finding the Element Where the Event Occurred</h5>
*
* Since most events bubble from descendant elements up through the hierarchy until they're
* handled, we can observe an event on a container rather than individual elements within the
* container. This is sometimes called "event delegation". It's particularly handy for tables:
*
* <table id='records'>
* <thead>
* <tr><th colspan='2'>No record clicked</th></tr>
* </thead>
* <tbody>
* <tr data-recnum='1'><td>1</td><td>First record</td></tr>
* <tr data-recnum='2'><td>2</td><td>Second record</td></tr>
* <tr data-recnum='3'><td>3</td><td>Third record</td></tr>
* </tbody>
* </table>
*
* Instead of observing each cell or row, we can simply observe the table:
*
* $('records').observe('click', function(event) {
* var clickedRow;
* clickedRow = event.findElement('tr');
* if (clickedRow) {
* this.down('th').update("You clicked record #" + clickedRow.readAttribute("data-recnum"));
* }
* });
*
* When any row in the table is clicked, we update the table's first header cell saying which
* record was clicked. [[Event#findElement]] finds the row that was clicked, and `this` refers
* to the table we were observing.
*
* <h5>Stopping Observing the Event</h5>
*
* If we don't need to observe the event anymore, we can stop observing it with
* [[Event.stopObserving]] (aka [[Element#stopObserving]]).
*
* <h5>Using an Instance Method as a Handler</h5>
*
* If we want to use an instance method as a handler, we will probably want to use
* [[Function#bind]] to set the handler's context; otherwise, the context will be lost and
* `this` won't mean what we expect it to mean within the handler function. E.g.:
*
* var MyClass = Class.create({
* initialize: function(name, element) {
* this.name = name;
* element = $(element);
* if (element) {
* element.observe(this.handleClick.bind(this));
* }
* },
* handleClick: function(event) {
* alert("My name is " + this.name);
* },
* });
*
* Without the `bind`, when `handleClick` was triggered by the event, `this` wouldn't
* refer to the instance and so the alert wouldn't show the name. Because we used `bind`, it
* works correctly. See [[Function#bind]] for
* details. There's also [[Function#bindAsEventListener]], which is handy for certain very
* specific situations. (Normally, `bind` is all you need.)
*
* <h5>Side Notes</h5>
*
* Although Prototype smooths out most of the differences between browsers, the fundamental
* behavior of a browser implementation isn't changed. For example, the timing of the `change`
* or `blur` events varies a bit from browser to browser.
*
* <h5>Changes in 1.6.x</h5>
*
* Prior to Prototype 1.6, `observe` supported a fourth argument (`useCapture`), a boolean that
* indicated whether to use the browser's capturing phase or its bubbling phase. Since MSIE does
* not support the capturing phase, we removed this argument from 1.6, lest it give users the
* false impression that they can use the capturing phase in all browsers.
*
* 1.6 also introduced setting the `this` context to the element being observed, automatically
* extending the [[Event]] object, and the [[Event#findElement]] method.
*
**/
function observe(element, eventName, handler) {
element = $(element);
var responder = _createResponder(element, eventName, handler);
var name = _getDOMEventName(eventName),
responder = _createResponder(element, eventName, handler);
if (!responder) return element;
if (eventName.include(':')) {
// Custom event.
if (element.addEventListener)
element.addEventListener("dataavailable", responder, false);
else {
// We observe two IE-proprietarty events: one for custom events that
// bubble and one for custom events that do not bubble.
element.attachEvent("ondataavailable", responder);
element.attachEvent("onfilterchange", responder);
}
} else {
var actualEventName = _getDOMEventName(eventName);
// Ordinary event.
if (element.addEventListener)
element.addEventListener(actualEventName, responder, false);
else
element.attachEvent("on" + actualEventName, responder);
}
if (element.addEventListener)
element.addEventListener(name, responder, false);
else
element.attachEvent("on" + name, responder);
return element;
}
/**
* Event.stopObserving(element[, eventName[, handler]]) -> Element
* - element (Element | String): The element to stop observing, or its ID.
* - eventName (String): _(Optional)_ The name of the event to stop observing, in all lower case,
* without the "on"&nbsp;&mdash; e.g., "click" (not "onclick").
* - handler (Function): _(Optional)_ The handler to remove; must be the _exact same_ reference
* that was passed to [[Event.observe]] (see below.).
*
* Unregisters one or more event handlers.
*
* If `handler` is omitted, unregisters all event handlers on `element`
* for that `eventName`. If `eventName` is also omitted, unregisters _all_
* event handlers on `element`. (In each case, only affects handlers registered via Prototype.)
*
* <h5>Examples</h5>
*
* Assuming:
*
* $('foo').observe('click', myHandler);
*
* ...we can stop observing using that handler like so:
*
* $('foo').stopObserving('click', myHandler);
*
* If we want to remove _all_ 'click' handlers from 'foo', we leave off the handler argument:
*
* $('foo').stopObserving('click');
*
* If we want to remove _all_ handlers for _all_ events from 'foo' (perhaps we're about to remove
* it from the DOM), we simply omit both the handler and the event name:
*
* $('foo').stopObserving();
*
* <h5>A Common Error</h5>
*
* When using instance methods as observers, it's common to use [[Function#bind]] on them, e.g.:
*
* $('foo').observe('click', this.handlerMethod.bind(this));
*
* If you do that, __this will not work__ to unregister the handler:
*
* $('foo').stopObserving('click', this.handlerMethod.bind(this)); // <== WRONG
*
* [[Function#bind]] returns a _new_ function every time it's called, and so if you don't retain
* the reference you used when observing, you can't unhook that function specifically. (You can
* still unhook __all__ handlers for an event, or all handlers on the element entirely.)
*
* To do this, you need to keep a reference to the bound function:
*
* this.boundHandlerMethod = this.handlerMethod.bind(this);
* $('foo').observe('click', this.boundHandlerMethod);
*
* ...and then to remove:
*
* $('foo').stopObserving('click', this.boundHandlerMethod); // <== Right
*
**/
function stopObserving(element, eventName, handler) {
element = $(element);
var id = _getEventID(element), name = _getDOMEventName(eventName);
var registry = Element.retrieve(element, 'prototype_event_registry');
if (!registry) return element;
if (!eventName) {
// We stop observing all events.
// e.g.: $(element).stopObserving();
registry.each( function(pair) {
var eventName = pair.key;
stopObserving(element, eventName);
if (eventName && !handler) {
// If an event name is passed without a handler, we stop observing all
// handlers of that type.
_getRespondersForEvent(id, eventName).each(function(r) {
element.stopObserving(eventName, r.handler);
});
return element;
} else if (!eventName) {
// If both the event name and the handler are omitted, we stop observing
// _all_ handlers on the element.
Object.keys(_getCacheForID(id)).each(function(eventName) {
element.stopObserving(eventName);
});
return element;
}
var responders = registry.get(eventName);
if (!responders) return element;
if (!handler) {
// We stop observing all handlers for the given eventName.
// e.g.: $(element).stopObserving('click');
responders.each(function(r) {
stopObserving(element, eventName, r.handler);
});
return element;
}
var responder = responders.find( function(r) { return r.handler === handler; });
var responder = _findResponder(id, eventName, handler);
if (!responder) return element;
if (eventName.include(':')) {
// Custom event.
if (element.removeEventListener)
element.removeEventListener("dataavailable", responder, false);
else {
element.detachEvent("ondataavailable", responder);
element.detachEvent("onfilterchange", responder);
}
} else {
// Ordinary event.
var actualEventName = _getDOMEventName(eventName);
if (element.removeEventListener)
element.removeEventListener(actualEventName, responder, false);
else
element.detachEvent('on' + actualEventName, responder);
}
if (element.removeEventListener)
element.removeEventListener(name, responder, false);
else
element.detachEvent('on' + name, responder);
registry.set(eventName, responders.without(responder));
_destroyResponder(id, eventName, handler);
return element;
}
/**
* Event.fire(element, eventName[, memo[, bubble = true]]) -> Event
* - memo (?): Metadata for the event. Will be accessible through the
* event's `memo` property.
* - bubble (Boolean): Whether the event will bubble.
*
* Fires a custom event of name `eventName` with `element` as its target.
*
* Custom events must include a colon (`:`) in their names.
**/
function fire(element, eventName, memo, bubble) {
function fire(element, eventName, memo) {
element = $(element);
if (Object.isUndefined(bubble))
bubble = true;
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
@ -701,7 +295,7 @@
event.initEvent('dataavailable', true, true);
} else {
event = document.createEventObject();
event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
event.eventType = 'ondataavailable';
}
event.eventName = eventName;
@ -725,100 +319,60 @@
});
Element.addMethods({
/**
* Element.fire(@element, eventName[, memo[, bubble = true]]) -> Event
* See [[Event.fire]].
**/
fire: fire,
/**
* Element.observe(@element, eventName, handler) -> Element
* See [[Event.observe]].
**/
observe: observe,
/**
* Element.stopObserving(@element[, eventName[, handler]]) -> Element
* See [[Event.stopObserving]].
**/
stopObserving: stopObserving
});
/** section: DOM
* document
*
* Prototype extends the built-in `document` object with several convenience
* methods related to events.
**/
Object.extend(document, {
/**
* document.fire(eventName[, memo[, bubble = true]]) -> Event
* See [[Event.fire]].
**/
fire: fire.methodize(),
/**
* document.observe(eventName, handler) -> Element
* See [[Event.observe]].
**/
observe: observe.methodize(),
/**
* document.stopObserving([eventName[, handler]]) -> Element
* See [[Event.stopObserving]].
**/
stopObserving: stopObserving.methodize(),
/**
* document.loaded -> Boolean
*
* Whether the full DOM tree is ready for manipulation.
**/
loaded: false
});
// Export to the global scope.
if (window.Event) Object.extend(window.Event, Event);
if (window.Event) Object.extend(window.Event, Event)
else window.Event = Event;
})();
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
var timer;
function fireContentLoadedEvent() {
var _timer;
function _fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearTimeout(timer);
if (_timer) window.clearInterval(_timer);
document.fire("dom:loaded");
document.loaded = true;
document.fire('dom:loaded');
}
function checkReadyState() {
if (document.readyState === 'complete') {
document.stopObserving('readystatechange', checkReadyState);
fireContentLoadedEvent();
function _webkitContentLoadedCheck() {
var s = document.readyState;
if (s === "loaded" || s === "complete")
_fireContentLoadedEvent();
}
function _IEContentLoadedCheck() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
_fireContentLoadedEvent();
}
}
function pollDoScroll() {
try { document.documentElement.doScroll('left'); }
catch(e) {
timer = pollDoScroll.defer();
return;
}
fireContentLoadedEvent();
}
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
if (Prototype.Browser.WebKit) {
_timer = window.setInterval(_webkitContentLoadedCheck, 0);
Event.observe(window, "load", _fireContentLoadedEvent);
} else {
document.addEventListener("DOMContentLoaded",
_fireContentLoadedEvent, false);
}
} else {
document.observe('readystatechange', checkReadyState);
if (window == top)
timer = pollDoScroll.defer();
document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
$("__onDOMContentLoaded").onreadystatechange = _IEContentLoadedCheck;
}
// Worst-case fallback
Event.observe(window, 'load', fireContentLoadedEvent);
})();
})();

View File

@ -1,89 +1,19 @@
/** section: DOM
* Form
*
* Utilities for dealing with forms in the DOM.
*
* `Form` is a namespace for all things form-related, packed with form
* manipulation and serialization goodness. While it holds methods dealing
* with forms as a whole, its submodule [[Form.Element]] deals with specific
* form controls.
*
* Many of these methods are also available directly on `form` elements.
**/
var Form = {
/**
* Form.reset(form) -> Element
*
* Resets a form to its default values.
**/
reset: function(form) {
form = $(form);
form.reset();
$(form).reset();
return form;
},
/**
* Form.serializeElements(elements[, options]) -> String | Object
* - elements (Array): A collection of elements to include in the
* serialization.
* - options (Object): _(Optional)_ A set of options that affect the return
* value of the method.
*
* Serialize an array of form elements to an object or string suitable
* for [[Ajax]] requests.
*
* As per the HTML spec, disabled fields are not included.
*
* If multiple elements have the same name and we're returning an object,
* the value for that key in the object will be an array of the field values
* in the order they appeared on the array of elements.
*
* <h5>The Options</h5>
*
* The options allow you to control two things: What kind of return
* value you get (an object or a string), and whether and which `submit`
* fields are included in that object or string.
*
* If you do not supply an `options` object _at all_, the options
* `{ hash: false }` are used.
*
* If you supply an `options` object, it may have the following options:
* - `hash` ([[Boolean]]): `true` to return a plain object with keys and values
* (not a [[Hash]]; see below), `false` to return a String in query string
* format. If you supply an `options` object with no `hash` member, `hash`
* defaults to `true`. Note that this is __not__ the same as leaving off the
* `options` object entirely (see above).
* - `submit` ([[Boolean]] | [[String]]): In essence: If you omit this option the
* first submit button in the form is included; if you supply `false`,
* no submit buttons are included; if you supply the name of a submit
* button, the first button with that name is included. Note that the `false`
* value __must__ really be `false`, not _falsey_; falsey-but-not-false is
* like omitting the option.
*
* _(Deprecated)_ If you pass in a [[Boolean]] instead of an object for `options`, it
* is used as the `hash` option and all other options are defaulted.
*
* <h5>A <em>hash</em>, not a Hash</h5>
*
* If you opt to receive an object, it is a plain JavaScript object with keys
* and values, __not__ a [[Hash]]. All JavaScript objects are hashes in the lower-case
* sense of the word, which is why the option has that somewhat-confusing name.
**/
serializeElements: function(elements, options) {
// An earlier version accepted a boolean second parameter (hash) where
// the default if omitted was false; respect that, but if they pass in an
// options object (e.g., the new signature) but don't specify the hash option,
// default true, as that's the new preferred approach.
if (typeof options != 'object') options = { hash: !!options };
else if (Object.isUndefined(options.hash)) options.hash = true;
var key, value, submitted = false, submit = options.submit;
var data = elements.inject({ }, function(result, element) {
if (!element.disabled && element.name) {
key = element.name; value = $(element).getValue();
if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
submit !== false && (!submit || key == submit) && (submitted = true)))) {
submit !== false && (!submit || key == submit) && (submitted = true)))) {
if (key in result) {
// a key is already present; construct an array of values
if (!Object.isArray(result[key])) result[key] = [result[key]];
@ -94,65 +24,32 @@ var Form = {
}
return result;
});
return options.hash ? data : Object.toQueryString(data);
}
};
Form.Methods = {
/**
* Form.serialize(@form[, options]) -> String | Object
* - options (Object): A list of options that affect the return value
* of the method.
*
* Serialize form data to an object or string suitable for Ajax requests.
*
* See [[Form.serializeElements]] for details on the options.
**/
serialize: function(form, options) {
return Form.serializeElements(Form.getElements(form), options);
},
/**
* Form.getElements(@form) -> [Element...]
*
* Returns a collection of all controls within a form.
**/
getElements: function(form) {
var elements = $(form).getElementsByTagName('*'),
element,
arr = [ ],
serializers = Form.Element.Serializers;
// `length` is not used to prevent interference with
// length-named elements shadowing `length` of a nodelist
for (var i = 0; element = elements[i]; i++) {
arr.push(element);
}
return arr.inject([], function(elements, child) {
if (serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
})
return $A($(form).getElementsByTagName('*')).inject([],
function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
}
);
},
/**
* Form.getInputs(@form [, type [, name]]) -> [Element...]
* - type (String): A value for the `type` attribute against which to
* filter.
* - name (String): A value for the `name` attribute against which to
* filter.
*
* Returns a collection of all `INPUT` elements in a form.
*
* Use optional `type` and `name` arguments to restrict the search on
* these attributes.
**/
getInputs: function(form, typeName, name) {
form = $(form);
var inputs = form.getElementsByTagName('input');
if (!typeName && !name) return $A(inputs).map(Element.extend);
for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
var input = inputs[i];
if ((typeName && input.type != typeName) || (name && input.name != name))
@ -163,34 +60,18 @@ Form.Methods = {
return matchingInputs;
},
/**
* Form.disable(@form) -> Element
*
* Disables the form as a whole. Form controls will be visible but
* uneditable.
**/
disable: function(form) {
form = $(form);
Form.getElements(form).invoke('disable');
return form;
},
/**
* Form.enable(@form) -> Element
*
* Enables a fully- or partially-disabled form.
**/
enable: function(form) {
form = $(form);
Form.getElements(form).invoke('enable');
return form;
},
/**
* Form.findFirstElement(@form) -> Element
*
* Finds the first non-hidden, non-disabled control within the form.
**/
findFirstElement: function(form) {
var elements = $(form).getElements().findAll(function(element) {
return 'hidden' != element.type && !element.disabled;
@ -198,92 +79,45 @@ Form.Methods = {
var firstByIndex = elements.findAll(function(element) {
return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
}).sortBy(function(element) { return element.tabIndex }).first();
return firstByIndex ? firstByIndex : elements.find(function(element) {
return /^(?:input|select|textarea)$/i.test(element.tagName);
return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
});
},
/**
* Form.focusFirstElement(@form) -> Element
*
* Gives keyboard focus to the first element of the form. Returns the form.
**/
focusFirstElement: function(form) {
form = $(form);
form.findFirstElement().activate();
return form;
},
/**
* Form.request(@form[, options]) -> Ajax.Request
* - options (Object): Options to pass along to the `Ajax.Request`
* constructor.
*
* A convenience method for serializing and submitting the form via an
* [[Ajax.Request]] to the URL of the form's `action` attribute.
*
* The `options` parameter is passed to the `Ajax.Request` instance,
* allowing one to override the HTTP method and/or specify additional
* parameters and callbacks.
**/
request: function(form, options) {
form = $(form), options = Object.clone(options || { });
var params = options.parameters, action = form.readAttribute('action') || '';
if (action.blank()) action = window.location.href;
options.parameters = form.serialize(true);
if (params) {
if (Object.isString(params)) params = params.toQueryParams();
Object.extend(options.parameters, params);
}
if (form.hasAttribute('method') && !options.method)
options.method = form.method;
return new Ajax.Request(action, options);
}
};
/*--------------------------------------------------------------------------*/
/**
* Form.Element
*
* Utilities for dealing with form controls in the DOM.
*
* This is a collection of methods that assist in dealing with form controls.
* They provide ways to focus, serialize, disable/enable or extract current
* value from a specific control.
*
* Note that nearly all these methods are available directly on `input`,
* `select`, and `textarea` elements. Therefore, these are equivalent:
*
* Form.Element.activate('myfield');
* $('myfield').activate();
*
* Naturally, you should always prefer the shortest form suitable in a
* situation. Most of these methods also return the element itself (as
* indicated by the return type) for chainability.
**/
Form.Element = {
/**
* Form.Element.focus(element) -> Element
*
* Gives keyboard focus to an element. Returns the element.
**/
focus: function(element) {
$(element).focus();
return element;
},
/**
* Form.Element.select(element) -> Element
*
* Selects the current text in a text input. Returns the element.
**/
select: function(element) {
$(element).select();
return element;
@ -291,13 +125,6 @@ Form.Element = {
};
Form.Element.Methods = {
/**
* Form.Element.serialize(@element) -> String
*
* Returns a URL-encoded string representation of a form control in the
* `name=value` format.
**/
serialize: function(element) {
element = $(element);
if (!element.disabled && element.name) {
@ -310,28 +137,13 @@ Form.Element.Methods = {
}
return '';
},
/** alias of: $F
* Form.Element.getValue(@element) -> String | Array
*
* Returns the current value of a form control.
*
* A string is returned for most controls; only multiple `select` boxes
* return an array of values.
*
* The global shortcut for this method is [[$F]].
**/
getValue: function(element) {
element = $(element);
var method = element.tagName.toLowerCase();
return Form.Element.Serializers[method](element);
},
/**
* Form.Element.setValue(@element, value) -> Element
*
* Sets `value` to be the value of the form control. Returns the element.
**/
setValue: function(element, value) {
element = $(element);
var method = element.tagName.toLowerCase();
@ -339,59 +151,32 @@ Form.Element.Methods = {
return element;
},
/**
* Form.Element.clear(@element) -> Element
*
* Clears the contents of a text input. Returns the element.
**/
clear: function(element) {
$(element).value = '';
return element;
},
/**
* Form.Element.present(@element) -> Element
*
* Returns `true` if a text input has contents, `false` otherwise.
**/
present: function(element) {
return $(element).value != '';
},
/**
* Form.Element.activate(@element) -> Element
*
* Gives focus to a form control and selects its contents if it is a text
* input.
**/
activate: function(element) {
element = $(element);
try {
element.focus();
if (element.select && (element.tagName.toLowerCase() != 'input' ||
!(/^(?:button|reset|submit)$/i.test(element.type))))
!['button', 'reset', 'submit'].include(element.type)))
element.select();
} catch (e) { }
return element;
},
/**
* Form.Element.disable(@element) -> Element
*
* Disables a form control, effectively preventing its value from changing
* until it is enabled again.
**/
disable: function(element) {
element = $(element);
element.disabled = true;
return element;
},
/**
* Form.Element.enable(@element) -> Element
*
* Enables a previously disabled form control.
**/
enable: function(element) {
element = $(element);
element.disabled = false;
@ -402,10 +187,6 @@ Form.Element.Methods = {
/*--------------------------------------------------------------------------*/
var Field = Form.Element;
/** section: DOM, related to: Form
* $F(element) -> String | Array
**/
var $F = Form.Element.Methods.getValue;
/*--------------------------------------------------------------------------*/
@ -413,7 +194,7 @@ var $F = Form.Element.Methods.getValue;
Form.Element.Serializers = {
input: function(element, value) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'checkbox':
case 'radio':
return Form.Element.Serializers.inputSelector(element, value);
default:
@ -430,10 +211,10 @@ Form.Element.Serializers = {
if (Object.isUndefined(value)) return element.value;
else element.value = value;
},
select: function(element, value) {
if (Object.isUndefined(value))
return this[element.type == 'select-one' ?
return this[element.type == 'select-one' ?
'selectOne' : 'selectMany'](element);
else {
var opt, currentValue, single = !Object.isArray(value);
@ -450,23 +231,23 @@ Form.Element.Serializers = {
}
}
},
selectOne: function(element) {
var index = element.selectedIndex;
return index >= 0 ? this.optionValue(element.options[index]) : null;
},
selectMany: function(element) {
var values, length = element.length;
if (!length) return null;
for (var i = 0, values = []; i < length; i++) {
var opt = element.options[i];
if (opt.selected) values.push(this.optionValue(opt));
}
return values;
},
optionValue: function(opt) {
// extend element because hasAttribute may not be native
return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
@ -475,49 +256,13 @@ Form.Element.Serializers = {
/*--------------------------------------------------------------------------*/
/** section: DOM
* Abstract
**/
/**
* class Abstract.TimedObserver
*
* An abstract DOM element observer class, subclasses of which can be used to periodically
* check a value and trigger a callback when the value has changed.
*
* A `TimedObserver` object will try to check a value using the `getValue()`
* instance method which must be defined by the subclass. There are two out-of-the-box subclasses:
* [[Form.Observer]], which serializes a form and triggers when the result has changed; and
* [[Form.Element.Observer]], which triggers when the value of a given form field changes.
*
* <h5>Creating Your Own TimedObserver Implementations</h5>
*
* It's easy to create your own `TimedObserver` implementations: Simply subclass `TimedObserver`
* and provide the `getValue()` method. For example, this is the complete source code for
* [[Form.Element.Observer]]:
*
* Form.Element.Observer = Class.create(Abstract.TimedObserver, {
* getValue: function() {
* return Form.Element.getValue(this.element);
* }
* });
**/
Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
/**
* new Abstract.TimedObserver(element, frequency, callback)
* - element (String | Element): The DOM element to watch. Can be an element instance or an ID.
* - frequency (Number): The frequency, in seconds&nbsp;&mdash; e.g., 0.33 to check for changes every
* third of a second.
* - callback (Function): The callback to trigger when the value changes.
*
* Initializes an `Abstract.TimedObserver`; used by subclasses.
**/
initialize: function($super, element, frequency, callback) {
$super(callback, frequency);
this.element = $(element);
this.lastValue = this.getValue();
},
execute: function() {
var value = this.getValue();
if (Object.isString(this.lastValue) && Object.isString(value) ?
@ -528,48 +273,13 @@ Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
}
});
/**
* class Form.Element.Observer < Abstract.TimedObserver
*
* An [[Abstract.TimedObserver]] subclass that watches for changes to a form field's value.
* This triggers the callback when the form field's value (according to
* [[Form.Element#getValue]]) changes. (Note that when the value actually changes can vary from
* browser to browser, particularly with `select` boxes.)
**/
Form.Element.Observer = Class.create(Abstract.TimedObserver, {
/**
* new Form.Element.Observer(element, frequency, callback)
* - element (String | Element): The form element to watch. Can be an element instance or an ID.
* - frequency (Number): The frequency, in seconds&nbsp;&mdash; e.g., 0.33 to check for changes every
* third of a second.
* - callback (Function): The callback to trigger when the value changes.
*
* Creates a Form.Element.Observer.
**/
getValue: function() {
return Form.Element.getValue(this.element);
}
});
/**
* class Form.Observer < Abstract.TimedObserver
*
* An [[Abstract.TimedObserver]] subclass that watches for changes to a form.
* The callback is triggered when the form changes&nbsp;&mdash; e.g., when any of its fields' values
* changes, when fields are added/removed, etc.; anything that affects the serialized
* form of the form (see [[Form#serialize]]).
**/
Form.Observer = Class.create(Abstract.TimedObserver, {
/**
* new Form.Observer(element, frequency, callback)
* - element (String | Element): The element of the form to watch. Can be an element
* instance or an ID.
* - frequency (Number): The frequency, in seconds -- e.g., 0.33 to check for changes every
* third of a second.
* - callback (Function): The callback to trigger when the form changes.
*
* Creates a Form.Observer.
**/
getValue: function() {
return Form.serialize(this.element);
}
@ -577,21 +287,18 @@ Form.Observer = Class.create(Abstract.TimedObserver, {
/*--------------------------------------------------------------------------*/
/**
* class Abstract.EventObserver
**/
Abstract.EventObserver = Class.create({
initialize: function(element, callback) {
this.element = $(element);
this.callback = callback;
this.lastValue = this.getValue();
if (this.element.tagName.toLowerCase() == 'form')
this.registerFormCallbacks();
else
this.registerCallback(this.element);
},
onElementEvent: function() {
var value = this.getValue();
if (this.lastValue != value) {
@ -599,15 +306,15 @@ Abstract.EventObserver = Class.create({
this.lastValue = value;
}
},
registerFormCallbacks: function() {
Form.getElements(this.element).each(this.registerCallback, this);
},
registerCallback: function(element) {
if (element.type) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'checkbox':
case 'radio':
Event.observe(element, 'click', this.onElementEvent.bind(this));
break;
@ -615,22 +322,16 @@ Abstract.EventObserver = Class.create({
Event.observe(element, 'change', this.onElementEvent.bind(this));
break;
}
}
}
}
});
/**
* class Form.Element.EventObserver < Abstract.EventObserver
**/
Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
/**
* class Form.EventObserver < Abstract.EventObserver
**/
Form.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.serialize(this.element);

View File

@ -1,66 +1,746 @@
/** section: DOM, related to: Prototype.Selector
* $$(expression...) -> [Element...]
*
* Returns all elements in the document that match the provided CSS selectors.
**/
/* 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. */
window.$$ = function() {
var expression = $A(arguments).join(', ');
return Prototype.Selector.select(expression, document);
};
var Selector = 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() {
if (!Prototype.BrowserFeatures.XPath) return false;
var e = this.expression;
/**
* Prototype.Selector
*
* A namespace that acts as a wrapper around
* the choosen selector engine (Sizzle by default).
*
**/
// 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;
// Implementation provided by selector engine.
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');
/**
* 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.
**/
// 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;
// Implementation provided by selector engine.
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;"];
/**
* 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]);
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
p = ps[i];
if (m = e.match(p)) {
this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
new Template(c[i]).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;
if (Selector._cache[e]) {
this.xpath = Selector._cache[e]; return;
}
this.matcher = ['.//*'];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
if (m = e.match(ps[i])) {
this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
new Template(x[i]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.xpath = this.matcher.join('');
Selector._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();
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 = Selector.patterns, as = Selector.assertions;
var le, p, m;
while (e && le !== e && (/\S/).test(e)) {
le = e;
for (var i in ps) {
p = ps[i];
if (m = e.match(p)) {
// use the Selector.assertions methods unless the selector
// is too complex.
if (as[i]) {
this.tokens.push([i, 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;
alert(isIgnored);
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;
var exclusion = [];
while (e && le != e && (/\S/).test(e)) {
le = e;
for (var i in p) {
if (m = e.match(p[i])) {
v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).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)
laterSibling: /^\s*~\s*/,
child: /^\s*>\s*/,
adjacent: /^\s*\+\s*/,
descendant: /^\s/,
// selectors follow
tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
id: /^#([\w\-\*]+)(\b|$)/,
className: /^\.([\w\-\*]+)(\b|$)/,
pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
attr: /\[((?:[\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(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
node._countedByPrototype = undefined;
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++)
if (!(n = nodes[i])._countedByPrototype) {
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 (!targetNode) return [];
if (!nodes && root == document) return [targetNode];
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.endsWith(v); },
'*=': function(nv, v) { return 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 = 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;
},
findElement: function(elements, expression, index) {
if (Object.isNumber(expression)) {
index = expression; expression = false;
}
return Selector.matchElements(elements, expression || '*')[index || 0];
},
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;
},
// IE improperly serializes _countedByPrototype in (inner|outer)HTML.
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
node.removeAttribute('_countedByPrototype');
return nodes;
}
});
}
function $$() {
return Selector.findChildElements(document, $A(arguments));
}

View File

@ -1,48 +0,0 @@
/**
* == Language ==
* Additions to JavaScript's "standard library" and extensions to
* built-in JavaScript objects.
**/
var Abstract = { };
/** section: Language
* Try
**/
/**
* Try.these(function...) -> ?
* - function (Function): A function that may throw an exception.
*
* Accepts an arbitrary number of functions and returns the result of the
* first one that doesn't throw an error.
**/
var Try = {
these: function() {
var returnValue;
for (var i = 0, length = arguments.length; i < length; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
break;
} catch (e) { }
}
return returnValue;
}
};
//= require "lang/class"
//= require "lang/object"
//= require "lang/function"
//= require "lang/date"
//= require "lang/regexp"
//= require "lang/periodical_executer"
//= require "lang/string"
//= require "lang/template"
//= require "lang/enumerable"
//= require "lang/array"
//= require "lang/hash"
//= require "lang/number"
//= require "lang/range"

View File

@ -1,205 +1,64 @@
/** section: Language, related to: Array
* $A(iterable) -> Array
*
* Accepts an array-like collection (anything with numeric indices) and returns
* its equivalent as an actual Array object.
* This method is a convenience alias of [[Array.from]], but is the preferred way
* of casting to an Array.
**/
function $A(iterable) {
if (!iterable) return [];
// Safari <2.0.4 crashes when accessing property of a node list with property accessor.
// It nevertheless works fine with `in` operator, which is why we use it here
if ('toArray' in Object(iterable)) return iterable.toArray();
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
/** section: Language, related to: Array
* $w(string) -> Array
* - string (String): A string with zero or more spaces.
*
* Splits a string into an array, treating all whitespace as delimiters.
*
* Equivalent to Ruby's `%w{foo bar}` or Perl's `qw(foo bar)`.
**/
if (Prototype.Browser.WebKit) {
$A = function(iterable) {
if (!iterable) return [];
// In Safari, only use the `toArray` method if it's not a NodeList.
// A NodeList is a function, has an function `item` property, and a numeric
// `length` property. Adapted from Google Doctype.
if (!(typeof iterable === 'function' && typeof iterable.length ===
'number' && typeof iterable.item === 'function') && iterable.toArray)
return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
};
}
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
return string ? string.split(/\s+/) : [];
}
/** alias of: $A
* Array.from(iterable) -> Array
**/
Array.from = $A;
/** section: Language
* class Array
* includes Enumerable
*
* Prototype extends all native JavaScript arrays with quite a few powerful
* methods.
*
* This is done in two ways:
*
* * It mixes in the [[Enumerable]] module, which brings in a ton of methods.
* * It adds quite a few extra methods, which are documented in this section.
*
* With Prototype, arrays become much, much more than the trivial objects we
* used to manipulate, limiting ourselves to using their `length` property and
* their `[]` indexing operator. They become very powerful objects that
* greatly simplify the code for 99% of the common use cases involving them.
*
* <h5>Why you should stop using for...in to iterate</h5>
*
* Many JavaScript authors have been misled into using the `for...in` JavaScript
* construct to loop over array elements. This kind of code just won't work
* with Prototype.
*
* The ECMA 262 standard, which defines ECMAScript 3rd edition, supposedly
* implemented by all major browsers including MSIE, defines ten methods
* on Array (&sect;15.4.4), including nice methods like `concat`, `join`, `pop`, and
* `push`.
*
* This same standard explicitly defines that the `for...in` construct (&sect;12.6.4)
* exists to enumerate the properties of the object appearing on the right side
* of the `in` keyword. Only properties specifically marked as _non-enumerable_
* are ignored by such a loop. By default, the `prototype` and `length`
* properties are so marked, which prevents you from enumerating over array
* methods when using for...in. This comfort led developers to use `for...in` as a
* shortcut for indexing loops, when it is not its actual purpose.
*
* However, Prototype has no way to mark the methods it adds to
* `Array.prototype` as non-enumerable. Therefore, using `for...in` on arrays
* when using Prototype will enumerate all extended methods as well, such as
* those coming from the [[Enumerable]] module, and those Prototype puts in the
* Array namespace (listed further below).
*
* <h5>What you should use instead</h5>
*
* You can revert to vanilla loops:
*
* for (var index = 0; index < myArray.length; ++index) {
* var item = myArray[index];
* // Your code working on item here...
* }
*
* Or you can use iterators, such as [[Array#each]]:
*
* myArray.each(function(item) {
* // Your code working on item here...
* });
*
*
* The inability to use `for...in` on arrays is not much of a burden: as you'll
* see, most of what you used to loop over arrays for can be concisely done
* using the new methods provided by Array or the mixed-in [[Enumerable]]
* module. So manual loops should be fairly rare.
*
*
* <h5>A note on performance</h5>
*
* Should you have a very large array, using iterators with lexical closures
* (anonymous functions that you pass to the iterators and that get invoked at
* every loop iteration) in methods like [[Array#each]] &mdash; _or_ relying on
* repetitive array construction (such as uniq), may yield unsatisfactory
* performance. In such cases, you're better off writing manual indexing loops,
* but take care then to cache the length property and use the prefix `++`
* operator:
*
* // Custom loop with cached length property: maximum full-loop
* // performance on very large arrays!
* for (var index = 0, len = myArray.length; index < len; ++index) {
* var item = myArray[index];
* // Your code working on item here...
* }
*
**/
(function() {
var arrayProto = Array.prototype,
slice = arrayProto.slice,
_each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
function each(iterator) {
for (var i = 0, length = this.length; i < length; i++)
iterator(this[i]);
}
if (!_each) _each = each;
/**
* Array#clear() -> Array
*
* Clears the array (makes it empty) and returns the array reference.
*
* <h5>Example</h5>
*
* var guys = ['Sam', 'Justin', 'Andrew', 'Dan'];
* guys.clear();
* // -> []
* guys
* // -> []
**/
function clear() {
this.length = 0;
return this;
}
/**
* Array#first() -> ?
*
* Returns the array's first item (e.g., `array[0]`).
**/
function first() {
return this[0];
}
/**
* Array#last() -> ?
*
* Returns the array's last item (e.g., `array[array.length - 1]`).
**/
function last() {
return this[this.length - 1];
}
/**
* Array#compact() -> Array
*
* Returns a **copy** of the array without any `null` or `undefined` values.
*
* <h5>Example</h5>
*
* var orig = [undefined, 'A', undefined, 'B', null, 'C'];
* var copy = orig.compact();
* // orig -> [undefined, 'A', undefined, 'B', null, 'C'];
* // copy -> ['A', 'B', 'C'];
**/
function compact() {
return this.select(function(value) {
return value != null;
});
}
/**
* Array#flatten() -> Array
*
* Returns a flattened (one-dimensional) copy of the array, leaving
* the original array unchanged.
*
* Nested arrays are recursively injected inline. This can prove very
* useful when handling the results of a recursive collection algorithm,
* for instance.
*
* <h5>Example</h5>
*
* var a = ['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]];
* var b = a.flatten();
* // a -> ['frank', ['bob', 'lisa'], ['jill', ['tom', 'sally']]]
* // b -> ['frank', 'bob', 'lisa', 'jill', 'tom', 'sally']
**/
function flatten() {
return this.inject([], function(array, value) {
if (Object.isArray(value))
@ -209,21 +68,6 @@ Array.from = $A;
});
}
/**
* Array#without(value[, value...]) -> Array
* - value (?): A value to exclude.
*
* Produces a new version of the array that does not contain any of the
* specified values, leaving the original array unchanged.
*
* <h5>Examples</h5>
*
* [3, 5, 6].without(3)
* // -> [5, 6]
*
* [3, 5, 6, 20].without(20, 6)
* // -> [3, 5]
**/
function without() {
var values = slice.call(arguments, 0);
return this.select(function(value) {
@ -231,49 +75,14 @@ Array.from = $A;
});
}
/**
* Array#reverse([inline = true]) -> Array
* - inline (Boolean): Whether to modify the array in place. Defaults to `true`.
* Clones the original array when `false`.
*
* Reverses the array's contents, optionally cloning it first.
*
* <h5>Examples</h5>
*
* // Making a copy
* var nums = [3, 5, 6, 1, 20];
* var rev = nums.reverse(false);
* // nums -> [3, 5, 6, 1, 20]
* // rev -> [20, 1, 6, 5, 3]
*
* // Working inline
* var nums = [3, 5, 6, 1, 20];
* nums.reverse();
* // nums -> [20, 1, 6, 5, 3]
**/
function reverse(inline) {
return (inline === false ? this.toArray() : this)._reverse();
return (inline !== false ? this : this.toArray())._reverse();
}
function reduce() {
return this.length > 1 ? this : this[0];
}
/**
* Array#uniq([sorted = false]) -> Array
* - sorted (Boolean): Whether the array has already been sorted. If `true`,
* a less-costly algorithm will be used.
*
* Produces a duplicate-free version of an array. If no duplicates are
* found, the original array is returned.
*
* On large arrays when `sorted` is `false`, this method has a potentially
* large performance cost.
*
* <h5>Examples</h5>
*
* [1, 3, 2, 1].uniq();
* // -> [1, 2, 3]
*
* ['A', 'a'].uniq();
* // -> ['A', 'a'] (because String comparison is case-sensitive)
**/
function uniq(sorted) {
return this.inject([], function(array, value, index) {
if (0 == index || (sorted ? array.last() != value : !array.include(value)))
@ -282,68 +91,24 @@ Array.from = $A;
});
}
/**
* Array#intersect(array) -> Array
* - array (Array): A collection of values.
*
* Returns an array containing every item that is shared between the two
* given arrays.
**/
function intersect(array) {
return this.uniq().findAll(function(item) {
function intersect(array) {
return this.uniq().findAll(function(item) {
return array.detect(function(value) { return item === value });
});
});
}
/** alias of: Array#clone
* Array#toArray() -> Array
**/
/**
* Array#clone() -> Array
*
* Returns a duplicate of the array, leaving the original array intact.
**/
function clone() {
return slice.call(this, 0);
}
/** related to: Enumerable#size
* Array#size() -> Number
*
* Returns the size of the array (e.g., `array.length`).
*
* This is just a local optimization of the mixed-in [[Enumerable#size]]
* which avoids array cloning and uses the array's native length property.
**/
function size() {
return this.length;
}
/** related to: Object.inspect
* Array#inspect() -> String
*
* Returns the debug-oriented string representation of an array.
*
* <h5>Example</h5>
*
* ['Apples', {good: 'yes', bad: 'no'}, 3, 34].inspect()
* // -> "['Apples', [object Object], 3, 34]"
**/
function inspect() {
return '[' + this.map(Object.inspect).join(', ') + ']';
}
/** related to: Object.toJSON
* Array#toJSON() -> String
*
* Returns a JSON string representation of the array.
*
* <h5>Example</h5>
*
* ['a', {b: null}].toJSON();
* //-> '["a", {"b": null}]'
**/
function toJSON() {
var results = [];
this.each(function(object) {
@ -352,28 +117,7 @@ Array.from = $A;
});
return '[' + results.join(', ') + ']';
}
/**
* Array#indexOf(item[, offset = 0]) -> Number
* - item (?): A value that may or may not be in the array.
* - offset (Number): The number of initial items to skip before beginning
* the search.
*
* Returns the index of the first occurrence of `item` within the array,
* or `-1` if `item` doesn't exist in the array. `Array#indexOf` compares
* items using *strict equality* (`===`).
*
* <h5>Examples</h5>
*
* [3, 5, 6, 1, 20].indexOf(1)
* // -> 3
*
* [3, 5, 6, 1, 20].indexOf(90)
* // -> -1 (not found)
*
* ['1', '2', '3'].indexOf(1);
* // -> -1 (not found, 1 !== '1')
**/
function indexOf(item, i) {
i || (i = 0);
var length = this.length;
@ -382,42 +126,32 @@ Array.from = $A;
if (this[i] === item) return i;
return -1;
}
/** related to: Array#indexOf
* Array#lastIndexOf(item[, offset]) -> Number
* - item (?): A value that may or may not be in the array.
* - offset (Number): The number of items at the end to skip before beginning
* the search.
*
* Returns the position of the last occurrence of `item` within the array &mdash; or
* `-1` if `item` doesn't exist in the array.
**/
function lastIndexOf(item, i) {
i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
var n = this.slice(0, i).reverse().indexOf(item);
return (n < 0) ? n : i - n - 1;
}
// Replaces a built-in function. No PDoc needed.
function concat() {
var array = slice.call(this, 0), item;
var array = [];
for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
for (var i = 0, length = arguments.length; i < length; i++) {
item = arguments[i];
if (Object.isArray(item) && !('callee' in item)) {
for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
array.push(item[j]);
} else {
array.push(item);
if (Object.isArray(arguments[i])) {
for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
array.push(arguments[i][j]);
} else {
array.push(arguments[i]);
}
}
return array;
}
Object.extend(arrayProto, Enumerable);
if (!arrayProto._reverse)
arrayProto._reverse = arrayProto.reverse;
Object.extend(arrayProto, {
_each: _each,
clear: clear,
@ -427,6 +161,7 @@ Array.from = $A;
flatten: flatten,
without: without,
reverse: reverse,
reduce: reduce,
uniq: uniq,
intersect: intersect,
clone: clone,
@ -435,15 +170,9 @@ Array.from = $A;
inspect: inspect,
toJSON: toJSON
});
// fix for opera
var CONCAT_ARGUMENTS_BUGGY = (function() {
return [].concat(arguments)[0][0] !== 1;
})(1,2)
if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
// use native browser JS 1.6 implementation if available
if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
if (!'indexOf' in arrayProto) arrayProto.indexOf = indexOf;
if (!'lastIndexOf' in arrayProto) arrayProto.lastIndexOf = lastIndexOf;
if (!'concat' in arrayProto) arrayProto.concat = concat;
})();

View File

@ -1,168 +1,46 @@
/* Based on Alex Arnell's inheritance implementation. */
/** section: Language
* Class
*
* Manages Prototype's class-based OOP system.
*
* Refer to Prototype's web site for a [tutorial on classes and
* 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.
* - methods (Object): An object whose properties will be "mixed-in" to the
* new class. Any number of mixins can be added; later mixins take
* precedence.
*
* `Class.create` creates a class and returns a constructor function for
* instances of the class. Calling the constructor function (typically as
* part of a `new` statement) will invoke the class's `initialize` method.
*
* `Class.create` accepts two kinds of arguments. If the first argument is
* a `Class`, it's used as the new class's superclass, and all its methods
* are inherited. Otherwise, any arguments passed are treated as objects,
* and their methods are copied over ("mixed in") as instance methods of the
* new class. In cases of method name overlap, later arguments take
* precedence over earlier arguments.
*
* If a subclass overrides an instance method declared in a superclass, the
* subclass's method can still access the original method. To do so, declare
* the subclass's method as normal, but insert `$super` as the first
* argument. This makes `$super` available as a method for use within the
* function.
*
* To extend a class after it has been defined, use [[Class#addMethods]].
*
* For details, see the
* [inheritance tutorial](http://prototypejs.org/learn/class-inheritance)
* on the Prototype website.
**/
function subclass() {};
function create() {
var parent = null, properties = $A(arguments);
if (Object.isFunction(properties[0]))
parent = properties.shift();
function klass() {
this.initialize.apply(this, arguments);
}
Object.extend(klass, Class.Methods);
klass.superclass = parent;
klass.subclasses = [];
if (parent) {
var subclass = function() {};
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}
for (var i = 0, length = properties.length; i < length; i++)
for (var i = 0; i < properties.length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = klass;
return klass;
return klass;
}
/**
* Class#addMethods(methods) -> Class
* - methods (Object): The methods to add to the class.
*
* Adds methods to an existing class.
*
* `Class#addMethods` is a method available on classes that have been
* defined with `Class.create`. It can be used to add new instance methods
* to that class, or overwrite existing methods, after the class has been
* defined.
*
* New methods propagate down the inheritance chain. If the class has
* subclasses, those subclasses will receive the new methods &mdash; even in
* the context of `$super` calls. The new methods also propagate to instances
* of the class and of all its subclasses, even those that have already been
* instantiated.
*
* <h5>Examples</h5>
*
* var Animal = Class.create({
* initialize: function(name, sound) {
* this.name = name;
* this.sound = sound;
* },
*
* speak: function() {
* alert(this.name + " says: " + this.sound + "!");
* }
* });
*
* // subclassing Animal
* var Snake = Class.create(Animal, {
* initialize: function($super, name) {
* $super(name, 'hissssssssss');
* }
* });
*
* var ringneck = new Snake("Ringneck");
* ringneck.speak();
*
* //-> alerts "Ringneck says: hissssssss!"
*
* // adding Snake#speak (with a supercall)
* Snake.addMethods({
* speak: function($super) {
* $super();
* alert("You should probably run. He looks really mad.");
* }
* });
*
* ringneck.speak();
* //-> alerts "Ringneck says: hissssssss!"
* //-> alerts "You should probably run. He looks really mad."
*
* // redefining Animal#speak
* Animal.addMethods({
* speak: function() {
* alert(this.name + 'snarls: ' + this.sound + '!');
* }
* });
*
* ringneck.speak();
* //-> alerts "Ringneck snarls: hissssssss!"
* //-> alerts "You should probably run. He looks really mad."
**/
function addMethods(source) {
var ancestor = this.superclass && this.superclass.prototype,
properties = Object.keys(source);
// 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)
properties.push("valueOf");
}
var ancestor = this.superclass && this.superclass.prototype;
var properties = Object.keys(source);
if (!Object.keys({ toString: true }).length)
properties.push("toString", "valueOf");
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i], value = source[property];
if (ancestor && Object.isFunction(value) &&
value.argumentNames()[0] == "$super") {
value.argumentNames().first() == "$super") {
var method = value;
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
@ -173,14 +51,14 @@ var Class = (function() {
}
this.prototype[property] = value;
}
return this;
return this;
}
return {
create: create,
Methods: {
addMethods: addMethods
}
};
})();
})();

View File

@ -1,23 +1,3 @@
/** section: Language
* class Date
*
* Extensions to the built-in `Date` object.
**/
/**
* Date#toJSON() -> String
*
* Produces a string representation of the date in ISO 8601 format.
* The time zone is always UTC, as denoted by the suffix "Z".
*
* <h5>Example</h5>
*
* var d = new Date(1969, 11, 31, 19);
* d.getTimezoneOffset();
* //-> -180 (time offest is given in minutes.)
* d.toJSON();
* //-> '"1969-12-31T16:00:00Z"'
**/
Date.prototype.toJSON = function() {
return '"' + this.getUTCFullYear() + '-' +
(this.getUTCMonth() + 1).toPaddedString(2) + '-' +

View File

@ -1,111 +1,6 @@
/** section: Language
* mixin Enumerable
*
* `Enumerable` provides a large set of useful methods for enumerations &mdash;
* objects that act as collections of values. It is a cornerstone of
* Prototype.
*
* `Enumerable` is a _mixin_: a set of methods intended not for standaone
* use, but for incorporation into other objects.
*
* Prototype mixes `Enumerable` into several classes. The most visible cases
* are [[Array]] and [[Hash]], but you'll find it in less obvious spots as
* well, such as in [[ObjectRange]] and various DOM- or Ajax-related objects.
*
* <h5>The <code>context</code> parameter</h5>
*
* Every method of `Enumerable` that takes an iterator also takes the "context
* object" as the next (optional) parameter. The context object is what the
* iterator will be _bound_ to &mdash; what the keyword `this` will refer to inside
* the iterator.
*
* var myObject = {};
*
* ['foo', 'bar', 'baz'].each(function(name, index) {
* this[name] = index;
* }, myObject); // we have specified the context
*
* myObject;
* // -> { foo: 0, bar: 1, baz: 2}
*
* If there is no `context` argument, the iterator function will execute in
* the scope from which the `Enumerable` method itself was called.
*
* <h5>Mixing <code>Enumerable</code> into your own objects</h5>
*
* So, let's say you've created your very own collection-like object (say,
* some sort of Set, or perhaps something that dynamically fetches data
* ranges from the server side, lazy-loading style). You want to be able to
* mix `Enumerable` in (and we commend you for it). How do you go about this?
*
* The Enumerable module basically makes only one requirement on your object:
* it must provide a method named `_each` (note the leading underscore) that
* will accept a function as its unique argument, and will contain the actual
* "raw iteration" algorithm, invoking its argument with each element in turn.
*
* As detailed in the documentation for [[Enumerable#each]], `Enumerable`
* provides all the extra layers (handling iteration short-circuits, passing
* numeric indices, etc.). You just need to implement the actual iteration,
* as fits your internal structure.
*
* If you're still confused, just have a look at the Prototype source code for
* [[Array]], [[Hash]], or [[ObjectRange]]. They all begin with their own
* `_each` method, which should help you grasp the idea.
*
* Once you're done with this, you just need to mix `Enumerable` in, which
* you'll usually do before defining your methods, so as to make sure whatever
* overrides you provide for `Enumerable` methods will indeed prevail. In
* short, your code will probably end up looking like this:
*
*
* var YourObject = Class.create(Enumerable, {
* initialize: function() { // with whatever constructor arguments you need
* // Your construction code
* },
*
* _each: function(iterator) {
* // Your iteration code, invoking iterator at every turn
* },
*
* // Your other methods here, including Enumerable overrides
* });
*
* Then, obviously, your object can be used like this:
*
* var obj = new YourObject();
* // Populate the collection somehow
* obj.pluck('somePropName');
* obj.invoke('someMethodName');
* obj.size();
* // etc.
*
**/
var $break = { };
var Enumerable = (function() {
/**
* Enumerable#each(iterator[, context]) -> Enumerable
* - iterator (Function): A `Function` that expects an item in the
* collection as the first argument and a numerical index as the second.
* - context (Object): The scope in which to call `iterator`. Affects what
* the keyword `this` means inside `iterator`.
*
* Calls `iterator` for each item in the collection.
*
* <h5>Examples</h5>
*
* ['one', 'two', 'three'].each(alert);
* // Alerts "one", then alerts "two", then alerts "three"
*
* <h5>Built-In Variants</h5>
*
* Most of the common use cases for `each` are already available pre-coded
* as other methods on `Enumerable`. Whether you want to find the first
* matching item in an enumeration, or transform it, or determine whether it
* has any (or all) values matching a particular condition, `Enumerable`
* has a method to do that for you.
**/
function each(iterator, context) {
var index = 0;
try {
@ -117,35 +12,7 @@ var Enumerable = (function() {
}
return this;
}
/**
* Enumerable#eachSlice(number[, iterator = Prototype.K[, context]]) -> Enumerable
* - number (Number): The number of items to include in each slice.
* - iterator (Function): An optional function to use to transform each
* element before it's included in the slice; if this is not provided,
* the element itself is included.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Groups items into chunks of the given size. The final "slice" may have
* fewer than `number` items; it won't "pad" the last group with empty
* values. For that behavior, use [[Enumerable#inGroupsOf]].
*
* <h5>Example</h5>
*
* var students = [
* { name: 'Sunny', age: 20 },
* { name: 'Audrey', age: 21 },
* { name: 'Matt', age: 20 },
* { name: 'Amelie', age: 26 },
* { name: 'Will', age: 21 }
* ];
*
* students.eachSlice(3, function(student) {
* return student.name;
* });
* // -> [['Sunny', 'Audrey', 'Matt'], ['Amelie', 'Will']]
**/
function eachSlice(number, iterator, context) {
var index = -number, slices = [], array = this.toArray();
if (number < 1) return array;
@ -154,34 +21,6 @@ var Enumerable = (function() {
return slices.collect(iterator, context);
}
/**
* Enumerable#all([iterator = Prototype.K[, context]]) -> Boolean
* - iterator (Function): An optional function to use to evaluate
* each element in the enumeration; the function should return the value to
* test. If this is not provided, the element itself is tested.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Determines whether all the elements are "truthy" (boolean-equivalent to
* `true`), either directly or through computation by the provided iterator.
* Stops on the first falsy element found (e.g., the first element that
* is boolean-equivalent to `false`, such as `undefined`, `0`, or indeed
* `false`);
*
* <h5>Examples</h5>
*
* [].all();
* // -> true (empty arrays have no elements that could be falsy)
*
* $R(1, 5).all();
* // -> true (all values in [1..5] are truthy)
*
* [0, 1, 2].all();
* // -> false (with only one loop cycle: 0 is falsy)
*
* [9, 10, 15].all(function(n) { return n >= 10; });
* // -> false (the iterator returns false on 9)
**/
function all(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
@ -192,28 +31,6 @@ var Enumerable = (function() {
return result;
}
/**
* Enumerable#any([iterator = Prototype.K[, context]]) -> Boolean
* - iterator (Function): An optional function to use to evaluate each
* element in the enumeration; the function should return the value to
* test. If this is not provided, the element itself is tested.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Determines whether at least one element is truthy (boolean-equivalent to
* `true`), either directly or through computation by the provided iterator.
*
* <h5>Examples</h5>
*
* [].any();
* // -> false (empty arrays have no elements that could be truthy)
*
* $R(0, 2).any();
* // -> true (on the second loop, 1 is truthy)
*
* [2, 4, 6, 8, 10].any(function(n) { return n > 5; });
* // -> true (the iterator will return true on 6)
**/
function any(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
@ -224,29 +41,6 @@ var Enumerable = (function() {
return result;
}
/**
* Enumerable#collect([iterator = Prototype.K[, context]]) -> Array
* - iterator (Function): The iterator function to apply to each element
* in the enumeration.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns the result of applying `iterator` to each element. If no
* `iterator` is provided, the elements are simply copied to the
* returned array.
*
* <h5>Examples</h5>
*
* ['Hitch', "Hiker's", 'Guide', 'to', 'the', 'Galaxy'].collect(function(s) {
* return s.charAt(0).toUpperCase();
* });
* // -> ['H', 'H', 'G', 'T', 'T', 'G']
*
* $R(1,5).collect(function(n) {
* return n * n;
* });
* // -> [1, 4, 9, 16, 25]
**/
function collect(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
@ -255,22 +49,7 @@ var Enumerable = (function() {
});
return results;
}
/**
* Enumerable#detect(iterator[, context]) -> firstElement | undefined
* - iterator (Function): The iterator function to apply to each element
* in the enumeration.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns the first element for which the iterator returns a truthy value.
* Aliased by the [[Enumerable#find]] method.
*
* <h5>Example</h5>
*
* [1, 7, -2, -4, 5].detect(function(n) { return n < 0; });
* // -> -2
**/
function detect(iterator, context) {
var result;
this.each(function(value, index) {
@ -281,21 +60,7 @@ var Enumerable = (function() {
});
return result;
}
/**
* Enumerable#findAll(iterator[, context]) -> Array
* - iterator (Function): An iterator function to use to test the elements.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns all the elements for which the iterator returned a truthy value.
* For the opposite operation, see [[Enumerable#reject]].
*
* <h5>Example</h5>
*
* [1, 'two', 3, 'four', 5].findAll(Object.isString);
* // -> ['two', 'four']
**/
function findAll(iterator, context) {
var results = [];
this.each(function(value, index) {
@ -304,71 +69,21 @@ var Enumerable = (function() {
});
return results;
}
/**
* Enumerable#grep(filter[, iterator = Prototype.K[, context]]) -> Array
* - filter (RegExp | String | Object): The filter to apply to elements. This
* can be a `RegExp` instance, a regular expression [[String]], or any
* object with a `match` function.
* - iterator (Function): An optional function to apply to selected elements
* before including them in the result.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns an array containing all of the elements for which the given
* filter returns `true` (or a truthy value). If an iterator is provided,
* it is used to produce the returned value for each selected element; this
* is done *after* the element has been selected by the filter.
*
* If the given filter is a [[String]], it is converted into a `RegExp`
* object. To select elements, each element is passed into the filter's
* `match` function, which should return a truthy value to select the element
* or a falsy value not to. Note that the `RegExp` `match` function will
* convert elements to Strings to perform matching.
*
* <h5>Examples</h5>
*
* // Get all strings containing a repeated letter
* ['hello', 'world', 'this', 'is', 'cool'].grep(/(.)\1/);
* // -> ['hello', 'cool']
*
* // Get all numbers ending with 0 or 5 and subtract 1 from them
* $R(1, 30).grep(/[05]$/, function(n) { return n - 1; });
* // -> [4, 9, 14, 19, 24, 29]
**/
function grep(filter, iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
if (Object.isString(filter))
filter = new RegExp(RegExp.escape(filter));
filter = new RegExp(filter);
this.each(function(value, index) {
if (filter.match(value))
results.push(iterator.call(context, value, index));
});
return results;
}
/**
* Enumerable#include(object) -> Boolean
* - object (?): The object to look for.
*
* Determines whether a given object is in the enumerable or not,
* based on the `==` comparison operator (equality with implicit type
* conversion).
*
* <h5>Examples</h5>
*
* $R(1, 15).include(10);
* // -> true
*
* ['hello', 'world'].include('HELLO');
* // -> false ('hello' != 'HELLO')
*
* [1, 2, '3', '4', '5'].include(3);
* // -> true ('3' == 3)
**/
function include(object) {
if (Object.isFunction(this.indexOf))
if (this.indexOf(object) != -1) return true;
@ -382,33 +97,7 @@ var Enumerable = (function() {
});
return found;
}
/**
* Enumerable#inGroupsOf(number[, fillWith = null]) -> [group...]
* - number (Number): The number of items to include in each group.
* - fillWith (Object): An optional filler to use if the last group needs
* any; defaults to `null`.
*
* Like [[Enumerable#eachSlice]], but pads out the last chunk with the
* specified value if necessary and doesn't support the `iterator` function.
*
* <h5>Examples</h5>
*
* var students = [
* { name: 'Sunny', age: 20 },
* { name: 'Audrey', age: 21 },
* { name: 'Matt', age: 20 },
* { name: 'Amelie', age: 26 },
* { name: 'Will', age: 21 }
* ];
*
* students.inGroupsOf(2, { name: '', age: 0 });
* // -> [
* // [{ name: 'Sunny', age: 20 }, { name: 'Audrey', age: 21 }],
* // [{ name: 'Matt', age: 20 }, { name: 'Amelie', age: 26 }],
* // [{ name: 'Will', age: 21 }, { name: '', age: 0 }]
* // ]
**/
function inGroupsOf(number, fillWith) {
fillWith = Object.isUndefined(fillWith) ? null : fillWith;
return this.eachSlice(number, function(slice) {
@ -416,100 +105,21 @@ var Enumerable = (function() {
return slice;
});
}
/**
* Enumerable#inject(accumulator, iterator[, context]) -> accumulatedValue
* - accumulator (?): The initial value to which the `iterator` adds.
* - iterator (Function): An iterator function used to build the accumulated
* result.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Incrementally builds a result value based on the successive results
* of the iterator. This can be used for array construction, numerical
* sums/averages, etc.
*
* The `iterator` function is called once for each element in the
* enumeration, receiving the current value of the accumulator as its first
* argument, the element as its second argument, and the element's index as
* its third. It returns the new value for the accumulator.
*
* <h5>Examples</h5>
*
* $R(1,10).inject(0, function(acc, n) { return acc + n; });
* // -> 55 (sum of 1 to 10)
*
* ['a', 'b', 'c', 'd', 'e'].inject([], function(string, value, index) {
* if (index % 2 === 0) { // even numbers
* string += value;
* }
* return string;
* });
* // -> 'ace'
**/
function inject(memo, iterator, context) {
this.each(function(value, index) {
memo = iterator.call(context, memo, value, index);
});
return memo;
}
/**
* Enumerable#invoke(methodName[, arg...]) -> Array
* - methodName (String): The name of the method to invoke.
* - args (?): Optional arguments to pass to the method.
*
* Invokes the same method, with the same arguments, for all items in a
* collection. Returns an array of the results of the method calls.
*
* <h5>Examples</h5>
*
* ['hello', 'world'].invoke('toUpperCase');
* // -> ['HELLO', 'WORLD']
*
* ['hello', 'world'].invoke('substring', 0, 3);
* // -> ['hel', 'wor']
*
* $$('input').invoke('stopObserving', 'change');
* // -> Stops observing the 'change' event on all input elements,
* // returns an array of the element references.
**/
function invoke(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
}
/** related to: Enumerable#min
* Enumerable#max([iterator = Prototype.K[, context]]) -> maxValue
* - iterator (Function): An optional function to use to evaluate each
* element in the enumeration; the function should return the value to
* test. If this is not provided, the element itself is tested.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns the maximum element (or element-based `iterator` result), or
* `undefined` if the enumeration is empty. Elements are either compared
* directly, or by first calling `iterator` and comparing returned values.
* If multiple "max" elements (or results) are equivalent, the one closest
* to the end of the enumeration is returned.
*
* If provided, `iterator` is called with two arguments: The element being
* evaluated, and its index in the enumeration; it should return the value
* `max` should consider (and potentially return).
*
* <h5>Examples</h5>
*
* ['c', 'b', 'a'].max();
* // -> 'c'
*
* [1, 3, '3', 2].max();
* // -> '3' (because both 3 and '3' are "max", and '3' was later)
*
* ['zero', 'one', 'two'].max(function(item) { return item.length; });
* // -> 4
**/
function max(iterator, context) {
iterator = iterator || Prototype.K;
var result;
@ -520,36 +130,7 @@ var Enumerable = (function() {
});
return result;
}
/** related to: Enumerable#max
* Enumerable#min([iterator = Prototype.K[, context]]) -> minValue
* - iterator (Function): An optional function to use to evaluate each
* element in the enumeration; the function should return the value to
* test. If this is not provided, the element itself is tested.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns the minimum element (or element-based `iterator` result), or
* `undefined` if the enumeration is empty. Elements are either compared
* directly, or by first calling `iterator` and comparing returned values.
* If multiple "min" elements (or results) are equivalent, the one closest
* to the *beginning* of the enumeration is returned.
*
* If provided, `iterator` is called with two arguments: The element being
* evaluated, and its index in the enumeration; it should return the value
* `min` should consider (and potentially return).
*
* <h5>Examples</h5>
*
* ['c', 'b', 'a'].min();
* // -> 'a'
*
* [3, 1, '1', 2].min();
* // -> 1 (because both 1 and '1' are "min", and 1 was earlier)
*
* ['un', 'deux', 'trois'].min(function(item) { return item.length; });
* // -> 2
**/
function min(iterator, context) {
iterator = iterator || Prototype.K;
var result;
@ -560,34 +141,7 @@ var Enumerable = (function() {
});
return result;
}
/**
* Enumerable#partition([iterator = Prototype.K[, context]]) -> [TrueArray, FalseArray]
* - iterator (Function): An optional function to use to evaluate each
* element in the enumeration; the function should return the value to
* test. If this is not provided, the element itself is tested.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Partitions the elements in two groups: those regarded as true, and those
* considered false. By default, regular JavaScript boolean equivalence
* (e.g., truthiness vs. falsiness) is used, but an iterator can be provided
* that computes a boolean representation of the elements.
*
* Using `partition` is more efficient than using [[Enumerable#findAll]] and
* then using [[Enumerable#reject]] because the enumeration is only processed
* once.
*
* <h5>Examples</h5>
*
* ['hello', null, 42, false, true, , 17].partition();
* // -> [['hello', 42, true, 17], [null, false, undefined]]
*
* $R(1, 10).partition(function(n) {
* return 0 == n % 2;
* });
* // -> [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]
**/
function partition(iterator, context) {
iterator = iterator || Prototype.K;
var trues = [], falses = [];
@ -597,20 +151,7 @@ var Enumerable = (function() {
});
return [trues, falses];
}
/**
* Enumerable#pluck(property) -> Array
* - property (String): The name of the property to fetch.
*
* Pre-baked implementation for a common use-case of [[Enumerable#collect]]
* and [[Enumerable#each]]: fetching the same property for all of the
* elements. Returns an array of the property values.
*
* <h5>Example</h5>
*
* ['hello', 'world', 'this', 'is', 'nice'].pluck('length');
* // -> [5, 5, 4, 2, 4]
**/
function pluck(property) {
var results = [];
this.each(function(value) {
@ -618,21 +159,7 @@ var Enumerable = (function() {
});
return results;
}
/**
* Enumerable#reject(iterator[, context]) -> Array
* - iterator (Function): An iterator function to use to test the elements.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Returns all the elements for which the iterator returns a falsy value.
* For the opposite operation, see [[Enumerable#findAll]].
*
* <h5>Example</h5>
*
* [1, "two", 3, "four", 5].reject(Object.isString);
* // -> [1, 3, 5]
**/
function reject(iterator, context) {
var results = [];
this.each(function(value, index) {
@ -641,29 +168,7 @@ var Enumerable = (function() {
});
return results;
}
/**
* Enumerable#sortBy(iterator[, context]) -> Array
* - iterator (Function): The function to use to compute the criterion for
* each element in the enumeration.
* - context (Object): An optional object to use as `this` within
* calls to the iterator.
*
* Creates a custom-sorted array of the elements based on the criteria
* computed, for each element, by the iterator. Computed criteria must have
* well-defined ordering semantics (i.e. the `<` operator must exist between
* any two criteria).
*
* `sortBy` does not guarantee a *stable* sort; adjacent equivalent elements
* may be swapped.
*
* <h5>Example</h5>
*
* ['hello', 'world', 'this', 'is', 'nice'].sortBy(function(s) {
* return s.length;
* });
* // -> ['is', 'nice', 'this', 'world', 'hello']
**/
function sortBy(iterator, context) {
return this.map(function(value, index) {
return {
@ -675,56 +180,11 @@ var Enumerable = (function() {
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
}
/**
* Enumerable#toArray() -> Array
*
* Returns an Array containing the elements of the enumeration.
*
* <h5>Example</h5>
*
* $R(1, 5).toArray();
* // -> [1, 2, 3, 4, 5]
*
* $H({ name: 'Sunny', age: 20 }).toArray();
* // -> [['name', 'Sunny'], ['age', 20]]
**/
function toArray() {
return this.map();
}
/**
* Enumerable#zip(sequence...[, iterator = Prototype.K]) -> Array
* - sequence (Object): A sequence to zip with this enumerable (there can
* be several of these if desired).
* - iterator (Function): Optional function to use to transform the tuples
* once generated; this is always the last argument provided.
*
* Zips together (think of the zipper on a pair of trousers) 2+ sequences,
* returning a new array of tuples. Each tuple is an array containing one
* value per original sequence. Tuples can be transformed to something else
* by applying the optional `iterator` on them.
*
* If supplied, `iterator` is called with each tuple as its only argument
* and should return the value to use in place of that tuple.
*
* <h5>Examples</h5>
*
* var firstNames = ['Jane', 'Nitin', 'Guy'];
* var lastNames = ['Doe', 'Patel', 'Forcier'];
* var ages = [23, 41, 17];
*
* firstNames.zip(lastNames);
* // -> [['Jane', 'Doe'], ['Nitin', 'Patel'], ['Guy', 'Forcier']]
*
* firstNames.zip(lastNames, ages);
* // -> [['Jane', 'Doe', 23], ['Nitin', 'Patel', 41], ['Guy', 'Forcier', 17]]
*
* firstNames.zip(lastNames, ages, function(tuple) {
* return tuple[0] + ' ' + tuple[1] + ' is ' + tuple[2];
* });
* // -> ['Jane Doe is 23', 'Nitin Patel is 41', 'Guy Forcier is 17']
**/
function zip() {
var iterator = Prototype.K, args = $A(arguments);
if (Object.isFunction(args.last()))
@ -735,57 +195,15 @@ var Enumerable = (function() {
return iterator(collections.pluck(index));
});
}
/**
* Enumerable#size() -> Number
*
* Returns the size of the enumeration.
**/
function size() {
return this.toArray().length;
}
/**
* Enumerable#inspect() -> String
*
* Returns the debug-oriented string representation of the object.
**/
function inspect() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
/** alias of: Enumerable#collect
* Enumerable#map([iterator = Prototype.K[, context]]) -> Array
**/
/** alias of: Enumerable#any
* Enumerable#some([iterator = Prototype.K[, context]]) -> Boolean
**/
/** alias of: Enumerable#all
* Enumerable#every([iterator = Prototype.K[, context]]) -> Boolean
**/
/** alias of: Enumerable#findAll
* Enumerable#select(iterator[, context]) -> Array
**/
/** alias of: Enumerable#findAll
* Enumerable#filter(iterator[, context]) -> Array
**/
/** alias of: Enumerable#include
* Enumerable#member(object) -> Boolean
**/
/** alias of: Enumerable#toArray
* Enumerable#entries() -> Array
**/
/** alias of: Enumerable#detect
* Enumerable#find(iterator[, context]) -> firstElement | undefined
**/
return {
each: each,
eachSlice: eachSlice,

View File

@ -1,40 +1,17 @@
/** section: Language
* class Function
*
* Extensions to the built-in `Function` object.
**/
Object.extend(Function.prototype, (function() {
var slice = Array.prototype.slice;
function update(array, args) {
var arrayLength = array.length, length = args.length;
while (length--) array[arrayLength + length] = args[length];
return array;
}
function merge(array, args) {
array = slice.call(array, 0);
return update(array, args);
}
/**
* Function#argumentNames() -> Array
*
* Reads the argument names as stated in the function definition and returns
* the values as an array of strings (or an empty array if the function is
* defined without parameters).
*
* <h5>Examples</h5>
*
* function fn(foo, bar) {
* return foo + bar;
* }
* fn.argumentNames();
* //-> ['foo', 'bar']
*
* Prototype.emptyFunction.argumentNames();
* //-> []
**/
function argumentNames() {
var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
@ -42,69 +19,6 @@ Object.extend(Function.prototype, (function() {
return names.length == 1 && !names[0] ? [] : names;
}
/** related to: Function#bindAsEventListener
* Function#bind(context[, args...]) -> Function
* - context (Object): The object to bind to.
* - args (?): Optional additional arguments to curry for the function.
*
* Binds this function to the given `context` by wrapping it in another
* function and returning the wrapper. Whenever the resulting "bound"
* function is called, it will call the original ensuring that `this` is set
* to `context`. Also optionally curries arguments for the function.
*
* <h5>Examples</h5>
*
* A typical use of `Function#bind` is to ensure that a callback (event
* handler, etc.) that is an object method gets called with the correct
* object as its context (`this` value):
*
* var AlertOnClick = Class.create({
* initialize: function(msg) {
* this.msg = msg;
* },
* handleClick: function(event) {
* event.stop();
* alert(this.msg);
* }
* });
* var myalert = new AlertOnClick("Clicked!");
* $('foo').observe('click', myalert.handleClick); // <= WRONG
* // -> If 'foo' is clicked, the alert will be blank; "this" is wrong
* $('bar').observe('click', myalert.handleClick.bind(myalert)); // <= RIGHT
* // -> If 'bar' is clicked, the alert will be "Clicked!"
*
* `bind` can also *curry* (burn in) arguments for the function if you
* provide them after the `context` argument:
*
* var Averager = Class.create({
* initialize: function() {
* this.count = 0;
* this.total = 0;
* },
* add: function(addend) {
* ++this.count;
* this.total += addend;
* },
* getAverage: function() {
* return this.count == 0 ? NaN : this.total / this.count;
* }
* });
* var a = new Averager();
* var b = new Averager();
* var aAdd5 = a.add.bind(a, 5); // Bind to a, curry 5
* var aAdd10 = a.add.bind(a, 10); // Bind to a, curry 10
* var bAdd20 = b.add.bind(b, 20); // Bind to b, curry 20
* aAdd5();
* aAdd10();
* bAdd20();
* bAdd20();
* alert(a.getAverage());
* // -> Alerts "7.5" (average of [5, 10])
* alert(b.getAverage());
* // -> Alerts "20" (average of [20, 20])
*
* (To curry without binding, see [[Function#curry]].)
**/
function bind(context) {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var __method = this, args = slice.call(arguments, 1);
@ -114,61 +28,6 @@ Object.extend(Function.prototype, (function() {
}
}
/** related to: Function#bind
* Function#bindAsEventListener(context[, args...]) -> Function
* - context (Object): The object to bind to.
* - args (?): Optional arguments to curry after the event argument.
*
* An event-specific variant of [[Function#bind]] which ensures the function
* will recieve the current event object as the first argument when
* executing.
*
* It is not necessary to use `bindAsEventListener` for all bound event
* handlers; [[Function#bind]] works well for the vast majority of cases.
* `bindAsEventListener` is only needed when:
*
* - Using old-style DOM0 handlers rather than handlers hooked up via
* [[Event.observe]], because `bindAsEventListener` gets the event object
* from the right place (even on MSIE). (If you're using `Event.observe`,
* that's already handled.)
* - You want to bind an event handler and curry additional arguments but
* have those arguments appear after, rather than before, the event object.
* This mostly happens if the number of arguments will vary, and so you
* want to know the event object is the first argument.
*
* <h5>Example</h5>
*
* var ContentUpdater = Class.create({
* initialize: function(initialData) {
* this.data = Object.extend({}, initialData);
* },
* // On an event, update the content in the elements whose
* // IDs are passed as arguments from our data
* updateTheseHandler: function(event) {
* var argIndex, id, element;
* event.stop();
* for (argIndex = 1; argIndex < arguments.length; ++argIndex) {
* id = arguments[argIndex];
* element = $(id);
* if (element) {
* element.update(String(this.data[id]).escapeHTML());
* }
* }
* }
* });
* var cu = new ContentUpdater({
* dispName: 'Joe Bloggs',
* dispTitle: 'Manager <provisional>',
* dispAge: 47
* });
* // Using bindAsEventListener because of the variable arg lists:
* $('btnUpdateName').observe('click',
* cu.updateTheseHandler.bindAsEventListener(cu, 'dispName')
* );
* $('btnUpdateAll').observe('click',
* cu.updateTheseHandler.bindAsEventListener(cu, 'dispName', 'dispTitle', 'dispAge')
* );
**/
function bindAsEventListener(context) {
var __method = this, args = slice.call(arguments, 1);
return function(event) {
@ -177,30 +36,6 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#curry(args...) -> Function
* - args (?): The arguments to curry.
*
* *Curries* (burns in) arguments to a function, returning a new function
* that when called with call the original passing in the curried arguments
* (along with any new ones):
*
* function showArguments() {
* alert($A(arguments).join(', '));
* }
* showArguments(1, 2,, 3);
* // -> alerts "1, 2, 3"
*
* var f = showArguments.curry(1, 2, 3);
* f('a', 'b');
* // -> alerts "1, 2, 3, a, b"
*
* `Function#curry` works just like [[Function#bind]] without the initial
* context argument. Use `bind` if you need to curry arguments _and_ set
* context at the same time.
*
* The name "curry" comes from [mathematics](http://en.wikipedia.org/wiki/Currying).
**/
function curry() {
if (!arguments.length) return this;
var __method = this, args = slice.call(arguments, 0);
@ -210,108 +45,19 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#delay(timeout[, args...]) -> Number
* - timeout (Number): The time, in seconds, to wait before calling the
* function.
* - args (?): Optional arguments to pass to the function when calling it.
*
* Schedules the function to run after the specified amount of time, passing
* any arguments given.
*
* Behaves much like `window.setTimeout`, but the timeout is in seconds
* rather than milliseconds. Returns an integer ID that can be used to
* clear the timeout with `window.clearTimeout` before it runs.
*
* To schedule a function to run as soon as the interpreter is idle, use
* [[Function#defer]].
*
* <h5>Example</h5>
*
* function showMsg(msg) {
* alert(msg);
* }
* showMsg.delay(0.1, "Hi there!");
* // -> Waits a 10th of a second, then alerts "Hi there!"
**/
function delay(timeout) {
function delay(timeout) {
var __method = this, args = slice.call(arguments, 1);
timeout = timeout * 1000;
timeout = timeout * 1000
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
}
/**
* Function#defer(args...) -> Number
* - args (?): Optional arguments to pass into the function.
*
* Schedules the function to run as soon as the interpreter is idle.
*
* A "deferred" function will not run immediately; rather, it will run as soon
* as the interpreter's call stack is empty.
*
* Behaves much like `window.setTimeout` with a delay set to `0`. Returns an
* ID that can be used to clear the timeout with `window.clearTimeout` before
* it runs.
*
* <h5>Example</h5>
*
* function showMsg(msg) {
* alert(msg);
* }
*
* showMsg("One");
* showMsg.defer("Two");
* showMsg("Three");
* // Alerts "One", then "Three", then (after a brief pause) "Two"
* // Note that "Three" happens before "Two"
**/
function defer() {
var args = update([0.01], arguments);
return this.delay.apply(this, args);
}
/**
* Function#wrap(wrapper) -> Function
* - wrapper (Function): The function to use as a wrapper.
*
* Returns a function "wrapped" around the original function.
*
* `Function#wrap` distills the essence of aspect-oriented programming into
* a single method, letting you easily build on existing functions by
* specifying before and after behavior, transforming the return value, or
* even preventing the original function from being called.
*
* The wraper function is called with this signature:
*
* function wrapper(callOriginal[, args...])
*
* ...where `callOriginal` is a function that can be used to call the
* original (wrapped) function (or not, as appropriate). (`callOriginal` is
* not a direct reference to the original function, there's a layer of
* indirection in-between that sets up the proper context \[`this` value\] for
* it.)
*
* <h5>Example</h5>
*
* // Wrap String#capitalize so it accepts an additional argument
* String.prototype.capitalize = String.prototype.capitalize.wrap(
* function(callOriginal, eachWord) {
* if (eachWord && this.include(" ")) {
* // capitalize each word in the string
* return this.split(" ").invoke("capitalize").join(" ");
* } else {
* // proceed using the original function
* return callOriginal();
* }
* });
*
* "hello world".capitalize();
* // -> "Hello world" (only the 'H' is capitalized)
* "hello world".capitalize(true);
* // -> "Hello World" (both 'H' and 'W' are capitalized)
**/
function wrap(wrapper) {
var __method = this;
return function() {
@ -320,54 +66,6 @@ Object.extend(Function.prototype, (function() {
}
}
/**
* Function#methodize() -> Function
*
* Wraps the function inside another function that, when called, pushes
* `this` to the original function as the first argument (with any further
* arguments following it).
*
* The `methodize` method transforms the original function that has an
* explicit first argument to a function that passes `this` (the current
* context) as an implicit first argument at call time. It is useful when we
* want to transform a function that takes an object to a method of that
* object or its prototype, shortening its signature by one argument.
*
* <h5>Example</h5>
*
* // A function that sets a name on a target object
* function setName(target, name) {
* target.name = name;
* }
*
* // Use it
* obj = {};
* setName(obj, 'Fred');
* obj.name;
* // -> "Fred"
*
* // Make it a method of the object
* obj.setName = setName.methodize();
*
* // Use the method instead
* obj.setName('Barney');
* obj.name;
* // -> "Barney"
*
* The example above is quite simplistic. It's more useful to copy methodized
* functions to object prototypes so that new methods are immediately shared
* among instances. In the Prototype library, `methodize` is used in various
* places such as the DOM module, so that (for instance) you can hide an
* element either by calling the static version of `Element.hide` and passing in
* an element reference or ID, like so:
*
* Element.hide('myElement');
*
* ...or if you already have an element reference, just calling the
* methodized form instead:
*
* myElement.hide();
**/
function methodize() {
if (this._methodized) return this._methodized;
var __method = this;
@ -376,7 +74,7 @@ Object.extend(Function.prototype, (function() {
return __method.apply(null, a);
};
}
return {
argumentNames: argumentNames,
bind: bind,

View File

@ -1,85 +1,12 @@
/** section: Language, related to: Hash
* $H([object]) -> Hash
*
* Creates a `Hash`. This is purely a convenience wrapper around the Hash
* constructor, it does not do anything other than pass any argument it's
* given into the Hash constructor and return the result.
**/
function $H(object) {
return new Hash(object);
};
/** section: Language
* class Hash
* includes Enumerable
*
* A set of key/value pairs.
*
* `Hash` can be thought of as an associative array, binding unique keys to
* values (which are not necessarily unique), though it can not guarantee
* consistent order its elements when iterating. Because of the nature of
* JavaScript, every object is in fact a hash; but `Hash` adds a number of
* methods that let you enumerate keys and values, iterate over key/value
* pairs, merge two hashes together, and much more.
*
* <h5>Creating a hash</h5>
*
* You can create a Hash either via `new Hash()` or the convenience alias
* `$H()`; there is **no** difference between them. In either case, you may
* optionally pass in an object to seed the `Hash`. If you pass in a `Hash`,
* it will be cloned.
*
**/
var Hash = Class.create(Enumerable, (function() {
/**
* new Hash([object])
*
* Creates a new `Hash`. If `object` is given, the new hash will be populated
* with all the object's properties. See [[$H]].
**/
function initialize(object) {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
}
// Docs for #each even though technically it's implemented by Enumerable
/**
* Hash#each(iterator[, context]) -> Hash
* - iterator (Function): A function that expects each item in the `Hash`
* as the first argument and a numerical index as the second.
* - context (Object): The scope in which to call `iterator`. Determines what
* `this` means inside `iterator`.
*
* Iterates over the name/value pairs in the hash.
*
* This is actually just the [[Enumerable#each #each]] method from the
* mixed-in [[Enumerable]] module. It is documented here to describe the
* structure of the elements passed to the iterator and the order of
* iteration.
*
* The iterator's first argument (the "item") is an object with two
* properties:
*
* - `key`: the key name as a `String`
* - `value`: the corresponding value (which may be `undefined`)
*
* The order of iteration is implementation-dependent, as it relies on
* the order of the native `for..in` loop. Although most modern
* implementations exhibit *ordered* behavior, this is not standardized and
* may not always be the case, and so cannot be relied upon.
*
* <h5>Example</h5>
*
* var h = $H({version: 1.6, author: 'The Core Team'});
*
* h.each(function(pair) {
* alert(pair.key + ' = "' + pair.value + '"');
* });
* // Alerts 'version = "1.6"' and 'author = "The Core Team"'
* // -or-
* // Alerts 'author = "The Core Team"' and 'version = "1.6"'
**/
// Our _internal_ each
function _each(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
@ -89,174 +16,45 @@ var Hash = Class.create(Enumerable, (function() {
}
}
/**
* Hash#set(key, value) -> value
* - key (String): The key to use for this value.
* - value (?): The value to use for this key.
*
* Stores `value` in the hash using the key `key` and returns `value`.
*
* <h5>Example</h5>
*
* var h = $H();
* h.keys();
* // -> [] (initially empty)
* h.set('a', 'apple');
* // -> "apple"
* h.keys();
* // -> ["a"] (has the new entry)
* h.get('a');
* // -> "apple"
**/
function set(key, value) {
return this._object[key] = value;
}
/**
* Hash#get(key) -> value
*
* Returns the stored value for the given `key`.
*
* <h5>Examples</h5>
*
* var h = new Hash({a: 'apple', b: 'banana', c: 'coconut'});
* h.get('a');
* // -> 'apple'
**/
function get(key) {
// simulating poorly supported hasOwnProperty
if (this._object[key] !== Object.prototype[key])
return this._object[key];
}
/**
* Hash#unset(key) -> value
*
* Deletes the stored pair for the given `key` from the hash and returns its
* value.
*
* <h5>Example</h5>
*
* var h = new Hash({a: 'apple', b: 'banana', c: 'coconut'});
* h.keys();
* // -> ["a", "b", "c"]
* h.unset('a');
* // -> 'apple'
* h.keys();
* // -> ["b", "c"] ("a" is no longer in the hash)
**/
function unset(key) {
var value = this._object[key];
delete this._object[key];
return value;
}
/**
* Hash#toObject() -> Object
*
* Returns a cloned, vanilla object whose properties (and property values)
* match the keys (and values) from the hash.
*
* <h5>Example</h5>
*
* var h = new Hash({ a: 'apple', b: 'banana', c: 'coconut' });
* var obj = h.toObject();
* obj.a;
* // -> "apple"
**/
function toObject() {
return Object.clone(this._object);
}
/**
* Hash#keys() -> [String...]
*
* Provides an Array containing the keys for items stored in the hash.
*
* The order of the keys is not guaranteed.
*
* <h5>Example</h5>
*
* var h = $H({one: "uno", two: "due", three: "tre"});
* h.keys();
* // -> ["one", "three", "two"] (these may be in any order)
**/
function keys() {
return this.pluck('key');
}
/**
* Hash#values() -> Array
*
* Collects the values of the hash and returns them in an array.
*
* The order of the values is not guaranteed.
*
* <h5>Example</h5>
*
* var h = $H({one: "uno", two: "due", three: "tre"});
* h.values();
* // -> ["uno", "tre", "due"] (these may be in any order)
**/
function values() {
return this.pluck('value');
}
/**
* Hash#index(value) -> String
*
* Returns the first key in the hash whose value matches `value`.
* Returns `false` if there is no such key.
**/
function index(value) {
var match = this.detect(function(pair) {
return pair.value === value;
var match = this.detect(function(pair) {
return pair.value === value;
});
return match && match.key;
}
/**
* Hash#merge(object) -> Hash
* - object (Object | Hash): The object to merge with this hash to produce
* the resulting hash.
*
* Returns a new `Hash` instance with `object`'s key/value pairs merged in;
* this hash remains unchanged.
*
* To modify the original hash in place, use [[Hash#update]].
*
* <h5>Example</h5>
*
* var h = $H({one: "uno", two: "due"});
* var h2 = h.merge({three: "tre"});
* h.keys();
* // -> ["one", "two"] (unchanged)
* h2.keys();
* // -> ["one", "two", "three"] (has merged contents)
**/
function merge(object) {
return this.clone().update(object);
}
/**
* Hash#update(object) -> Hash
* - object (Object | Hash): The object to merge with this hash to produce
* the resulting hash.
*
* Updates a hash *in place* with the key/value pairs of `object`, returns
* the hash.
*
* `update` modifies the hash. To get a new hash instead, use
* [[Hash#merge]].
*
* <h5>Example</h5>
*
* var h = $H({one: "uno", two: "due"});
* h.update({three: "tre"});
* // -> h (a reference to the original hash)
* h.keys();
* // -> ["one", "two", "three"] (has merged contents)
**/
function update(object) {
return new Hash(object).inject(this, function(result, pair) {
result.set(pair.key, pair.value);
@ -264,55 +62,15 @@ var Hash = Class.create(Enumerable, (function() {
});
}
// Private. No PDoc necessary.
function toQueryPair(key, value) {
if (Object.isUndefined(value)) return key;
return key + '=' + encodeURIComponent(String.interpret(value));
}
/** related to: String#toQueryParams
* Hash#toQueryString() -> String
*
* Returns a URL-encoded string containing the hash's contents as query
* parameters according to the following rules:
*
* - An undefined value results a parameter with no value portion at all
* (simply the key name, no equal sign).
* - A null value results a parameter with a blank value (the key followed
* by an equal sign and nothing else).
* - A boolean value results a parameter with the value "true" or "false".
* - An Array value results in a parameter for each array element, in
* array order, each using the same key.
* - All keys and values are URI-encoded using JavaScript's native
* `encodeURIComponent` function.
*
* The order of pairs in the string is not guaranteed, other than the order
* of array values described above.
*
* <h5>Example</h5>
*
* $H({action: 'ship',
* order_id: 123,
* fees: ['f1', 'f2']
* }).toQueryString();
* // -> "action=ship&order_id=123&fees=f1&fees=f2"
*
* $H({comment: '',
* 'key with spaces': true,
* related_order: undefined,
* contents: null,
* 'label': 'a demo'
* }).toQueryString();
* // -> "comment=&key%20with%20spaces=true&related_order&contents=&label=a%20demo"
*
* // an empty hash is an empty query string:
* $H().toQueryString();
* // -> ""
**/
function toQueryString() {
return this.inject([], function(results, pair) {
var key = encodeURIComponent(pair.key), values = pair.value;
if (values && typeof values == 'object') {
if (Object.isArray(values))
return results.concat(values.map(toQueryPair.curry(key)));
@ -321,41 +79,20 @@ var Hash = Class.create(Enumerable, (function() {
}).join('&');
}
/** related to: Object.inspect
* Hash#inspect() -> String
*
* Returns the debug-oriented string representation of the Hash.
**/
function inspect() {
return '#<Hash:{' + this.map(function(pair) {
return pair.map(Object.inspect).join(': ');
}).join(', ') + '}>';
}
/** related to: Object.toJSON
* Hash#toJSON() -> String
*
* Returns a JSON string containing the keys and values in this hash.
*
* <h5>Example</h5>
*
* var h = $H({'a': 'apple', 'b': 23, 'c': false});
* h.toJSON();
* // -> {"a": "apple", "b": 23, "c": false}
**/
function toJSON() {
return Object.toJSON(this.toObject());
}
/**
* Hash#clone() -> Hash
*
* Returns a clone of this Hash.
**/
function clone() {
return new Hash(this);
}
return {
initialize: initialize,
_each: _each,

View File

@ -1,159 +1,42 @@
/** section: Language
* class Number
*
* Extensions to the built-in `Number` object.
*
* Prototype extends native JavaScript numbers in order to provide:
*
* * [[ObjectRange]] compatibility, through [[Number#succ]].
* * Numerical loops with [[Number#times]].
* * Simple utility methods such as [[Number#toColorPart]] and
* [[Number#toPaddedString]].
* * Instance-method aliases of many functions in the `Math` namespace.
*
**/
Object.extend(Number.prototype, (function() {
/**
* Number#toColorPart() -> String
*
* Produces a 2-digit hexadecimal representation of the number
* (which is therefore assumed to be in the \[0..255\] range, inclusive).
* Useful for composing CSS color strings.
*
* <h5>Example</h5>
*
* 10.toColorPart()
* // -> "0a"
**/
function toColorPart() {
return this.toPaddedString(2, 16);
}
/**
* Number#succ() -> Number
*
* Returns the successor of the current Number, as defined by current + 1.
* Used to make numbers compatible with ObjectRange.
**/
function succ() {
return this + 1;
}
/**
* Number#times(iterator[,context]) -> Number
* - iterator (Function): An iterator function to call.
* - context (Object): An optional context (`this` value) to use when
* calling `iterator`.
*
* Calls `iterator` the specified number of times, passing in a number as
* the first parameter. The number will be 0 on first call, 1 on second
* call, etc. `times` returns the number instance it was called on.
*
* <h5>Example</h5>
*
* (3).times(alert);
* // -> Alerts "0", then "1", then "2"; returns 3
*
* var obj = {count: 0, total: 0};
* function add(addend) {
* ++this.count;
* this.total += addend;
* }
* (4).times(add, obj);
* // -> 4
* obj.count;
* // -> 4
* obj.total;
* // -> 6 (e.g., 0 + 1 + 2 + 3)
**/
function times(iterator, context) {
$R(0, this, true).each(iterator, context);
return this;
}
/**
* Number#toPaddedString(length[, radix]) -> String
* - length (Number): The minimum length for the resulting string.
* - radix (Number): An optional radix for the string representation,
* defaults to 10 (decimal).
*
* Returns a string representation of the number padded with leading 0s so
* that the string's length is at least equal to `length`. Takes an optional
* `radix` argument which specifies the base to use for conversion.
*
* <h5>Examples</h5>
*
* (13).toPaddedString(4);
* // -> "0013"
*
* (13).toPaddedString(2);
* // -> "13"
*
* (13).toPaddedString(1);
* // -> "13"
*
* (13).toPaddedString(4, 16)
* // -> "000d"
*
* (13).toPaddedString(4, 2);
* // -> "1101"
**/
function toPaddedString(length, radix) {
var string = this.toString(radix || 10);
return '0'.times(length - string.length) + string;
}
/** related to: Object.toJSON
* Number#toJSON() -> String
*
* Returns a JSON string representation of the number.
**/
function toJSON() {
return isFinite(this) ? this.toString() : 'null';
}
/**
* Number#abs() -> Number
*
* Returns the absolute value of the number. Convenience method that simply
* calls `Math.abs` on this instance and returns the result.
**/
function abs() {
return Math.abs(this);
}
/**
* Number#round() -> Number
*
* Rounds the number to the nearest integer. Convenience method that simply
* calls `Math.round` on this instance and returns the result.
**/
function round() {
return Math.round(this);
}
/**
* Number#ceil() -> Number
*
* Returns the smallest integer greater than or equal to the number.
* Convenience method that simply calls `Math.ceil` on this instance and
* returns the result.
**/
function ceil() {
return Math.ceil(this);
}
/**
* Number#floor() -> Number
*
* Returns the largest integer less than or equal to the number.
* Convenience method that simply calls `Math.floor` on this instance and
* returns the result.
**/
function floor() {
return Math.floor(this);
}
return {
toColorPart: toColorPart,
succ: succ,

View File

@ -1,47 +1,10 @@
/** section: Language
* class Object
*
* Extensions to the built-in `Object` object.
*
* Because it is dangerous and invasive to augment `Object.prototype` (i.e.,
* add instance methods to objects), all these methods are static methods that
* take an `Object` as their first parameter.
*
**/
(function() {
var _toString = Object.prototype.toString;
/**
* Object.extend(destination, source) -> Object
* - destination (Object): The object to receive the new properties.
* - source (Object): The object whose properties will be duplicated.
*
* Copies all properties from the source to the destination object. Returns
* the destination object.
**/
function extend(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
}
/**
* Object.inspect(object) -> String
* - object (Object): The item to be inspected.
*
* Returns the debug-oriented string representation of the object.
*
* `undefined` and `null` are represented as such.
*
* Other types are checked for a `inspect` method. If there is one, it is
* used; otherwise, it reverts to the `toString` method.
*
* Prototype provides `inspect` methods for many types, both built-in and
* library-defined &mdash; among them `String`, `Array`, `Enumerable` and `Hash`.
* These attempt to provide useful string representations (from a
* developer's standpoint) for their respective types.
**/
function inspect(object) {
try {
if (isUndefined(object)) return 'undefined';
@ -53,19 +16,6 @@
}
}
/**
* Object.toJSON(object) -> String
* - object (Object): The object to be serialized.
*
* Returns a JSON string.
*
* `undefined` and `function` types have no JSON representation. `boolean`
* and `null` are coerced to strings.
*
* For other types, `Object.toJSON` looks for a `toJSON` method on `object`.
* If there is one, it is used; otherwise the object is treated like a
* generic `Object`.
**/
function toJSON(object) {
var type = typeof object;
switch (type) {
@ -89,189 +39,57 @@
return '{' + results.join(', ') + '}';
}
/**
* Object.toQueryString(object) -> String
* object (Object): The object whose property/value pairs will be converted.
*
* Turns an object into its URL-encoded query string representation.
*
* This is a form of serialization, and is mostly useful to provide complex
* parameter sets for stuff such as objects in the Ajax namespace (e.g.
* [[Ajax.Request]]).
*
* Undefined-value pairs will be serialized as if empty-valued. Array-valued
* pairs will get serialized with one name/value pair per array element. All
* values get URI-encoded using JavaScript's native `encodeURIComponent`
* function.
*
* The order of pairs in the serialized form is not guaranteed (and mostly
* irrelevant anyway) &mdash; except for array-based parts, which are serialized
* in array order.
**/
function toQueryString(object) {
return $H(object).toQueryString();
}
/**
* Object.toHTML(object) -> String
* - object (Object): The object to convert to HTML.
*
* Converts the object to its HTML representation.
*
* Returns the return value of `object`'s `toHTML` method if it exists; else
* runs `object` through [[String.interpret]].
**/
function toHTML(object) {
return object && object.toHTML ? object.toHTML() : String.interpret(object);
}
/**
* Object.keys(object) -> Array
* - object (Object): The object to pull keys from.
*
* Returns an array of the object's property names.
*
* Note that the order of the resulting array is browser-dependent &mdash; it
* relies on the `for&#8230;in` loop, for which the ECMAScript spec does not
* prescribe an enumeration order. Sort the resulting array if you wish to
* normalize the order of the object keys.
**/
function keys(object) {
var results = [];
var keys = [];
for (var property in object)
results.push(property);
return results;
keys.push(property);
return keys;
}
/**
* Object.values(object) -> Array
* - object (Object): The object to pull values from.
*
* Returns an array of the object's values.
*
* Note that the order of the resulting array is browser-dependent &mdash; it
* relies on the `for&#8230;in` loop, for which the ECMAScript spec does not
* prescribe an enumeration order.
*
* Also, remember that while property _names_ are unique, property _values_
* have no such constraint.
**/
function values(object) {
var results = [];
var values = [];
for (var property in object)
results.push(object[property]);
return results;
values.push(object[property]);
return values;
}
/**
* Object.clone(object) -> Object
* - object (Object): The object to clone.
*
* Creates and returns a shallow duplicate of the passed object by copying
* all of the original's key/value pairs onto an empty object.
*
* Do note that this is a _shallow_ copy, not a _deep_ copy. Nested objects
* will retain their references.
*
* <h5>Examples</h5>
*
* var original = {name: 'primaryColors', values: ['red', 'green', 'blue']};
* var copy = Object.clone(original);
* original.name;
* // -> "primaryColors"
* original.values[0];
* // -> "red"
* copy.name;
* // -> "primaryColors"
* copy.name = "secondaryColors";
* original.name;
* // -> "primaryColors"
* copy.name;
* // -> "secondaryColors"
* copy.values[0] = 'magenta';
* copy.values[1] = 'cyan';
* copy.values[2] = 'yellow';
* original.values[0];
* // -> "magenta" (it was a shallow copy, so they shared the array)
**/
function clone(object) {
return extend({ }, object);
}
/**
* Object.isElement(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is a DOM node of type 1; `false` otherwise.
**/
function isElement(object) {
return !!(object && object.nodeType == 1);
}
/**
* Object.isArray(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is an array; false otherwise.
**/
function isArray(object) {
return _toString.call(object) == "[object Array]";
}
var hasNativeIsArray = (typeof Array.isArray == 'function')
&& Array.isArray([]) && !Array.isArray({});
if (hasNativeIsArray) {
isArray = Array.isArray;
return object != null && typeof object === "object" &&
'splice' in object && 'join' in object;
}
/**
* Object.isHash(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is an instance of the [[Hash]] class; `false`
* otherwise.
**/
function isHash(object) {
return object instanceof Hash;
}
/**
* Object.isFunction(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `function`; `false` otherwise.
**/
function isFunction(object) {
return typeof object === "function";
}
/**
* Object.isString(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `string`; `false` otherwise.
**/
function isString(object) {
return _toString.call(object) == "[object String]";
return typeof object === "string";
}
/**
* Object.isNumber(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `number`; `false` otherwise.
**/
function isNumber(object) {
return _toString.call(object) == "[object Number]";
return typeof object === "number";
}
/**
* Object.isUndefined(object) -> Boolean
* - object (Object): The object to test.
*
* Returns `true` if `object` is of type `string`; `false` otherwise.
**/
function isUndefined(object) {
return typeof object === "undefined";
}

View File

@ -1,25 +1,4 @@
/** section: Language
* class PeriodicalExecuter
*
* A class that oversees the calling of a particular function periodically.
*
* `PeriodicalExecuter` shields you from multiple parallel executions of the
* `callback` function, should it take longer than the given interval to
* execute.
*
* This is especially useful if you use one to interact with the user at
* given intervals (e.g. use a prompt or confirm call): this will avoid
* multiple message boxes all waiting to be actioned.
**/
var PeriodicalExecuter = Class.create({
/**
* new PeriodicalExecuter(callback, frequency)
* - callback (Function): the function to be executed at each interval.
* - frequency (Number): the amount of time, in sections, to wait in between
* callbacks.
*
* Creates an `PeriodicalExecuter`.
**/
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
@ -35,12 +14,7 @@ var PeriodicalExecuter = Class.create({
execute: function() {
this.callback(this);
},
/**
* PeriodicalExecuter#stop() -> undefined
*
* Stops the periodical executer (there will be no further triggers).
**/
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
@ -49,18 +23,11 @@ var PeriodicalExecuter = Class.create({
onTimerEvent: function() {
if (!this.currentlyExecuting) {
// IE doesn't support `finally` statements unless all errors are caught.
// We mimic the behaviour of `finally` statements by duplicating code
// that would belong in it. First at the bottom of the `try` statement
// (for errorless cases). Secondly, inside a `catch` statement which
// rethrows any caught errors.
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
} catch(e) {
this.currentlyExecuting = false;
throw e;
}
}
}

View File

@ -1,51 +1,14 @@
/** section: Language
* class ObjectRange
* includes Enumerable
*
* A succession of values.
*
* An `ObjectRange` can model a range of any value that implements a `succ`
* method (which links that value to its "successor").
*
* Prototype provides such a method for [[Number]] and [[String]], but you
* are (of course) welcome to implement useful semantics in your own objects,
* in order to enable ranges based on them.
*
* `ObjectRange` mixes in [[Enumerable]], which makes ranges very versatile.
* It takes care, however, to override the default code for `include`, to
* achieve better efficiency.
*
* While `ObjectRange` does provide a constructor, the preferred way to obtain
* a range is to use the [[$R]] utility function, which is strictly equivalent
* (only way more concise to use).
**/
/** section: Language
* $R(start, end[, exclusive = false]) -> ObjectRange
*
* Creates a new ObjectRange object.
* This method is a convenience wrapper around the [[ObjectRange]] constructor,
* but $R is the preferred alias.
**/
function $R(start, end, exclusive) {
return new ObjectRange(start, end, exclusive);
}
var ObjectRange = Class.create(Enumerable, (function() {
/**
* new ObjectRange(start, end[, exclusive = false])
*
* Creates a new `ObjectRange`.
*
* The `exclusive` argument specifies whether `end` itself is a part of the
* range.
**/
function initialize(start, end, exclusive) {
this.start = start;
this.end = end;
this.exclusive = exclusive;
}
function _each(iterator) {
var value = this.start;
while (this.include(value)) {
@ -53,20 +16,15 @@ var ObjectRange = Class.create(Enumerable, (function() {
value = value.succ();
}
}
/**
* ObjectRange#include(value) -> Boolean
*
* Determines whether the value is included in the range.
**/
function include(value) {
if (value < this.start)
if (value < this.start)
return false;
if (this.exclusive)
return value < this.end;
return value <= this.end;
}
return {
initialize: initialize,
_each: _each,

View File

@ -1,25 +1,5 @@
/** section: Language
* class RegExp
*
* Extensions to the built-in `RegExp` object.
**/
/** alias of: RegExp#test
* RegExp#match(str) -> Boolean
*
* Return true if string matches the regular expression, false otherwise.
**/
RegExp.prototype.match = RegExp.prototype.test;
/**
* RegExp.escape(str) -> String
* - str (String): A string intended to be used in a `RegExp` constructor.
*
* Escapes any characters in the string that have special meaning in a
* regular expression.
*
* Use before passing a string into the `RegExp` constructor.
**/
RegExp.escape = function(str) {
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

View File

@ -1,20 +1,4 @@
/** section: Language
* class String
*
* Extensions to the built-in `String` class.
*
* Prototype enhances the `String` object with a series of useful methods for
* ranging from the trivial to the complex. Tired of stripping trailing
* whitespace? Try [[String#strip]]. Want to replace `replace`? Have a look at
* [[String#sub]] and [[String#gsub]]. Need to parse a query string? We have
* [[String#toQueryParams]].
**/
Object.extend(String, {
/**
* String.interpret(value) -> String
*
* Coerces `value` into a string. Returns an empty string for `null`.
**/
interpret: function(value) {
return value == null ? '' : String(value);
},
@ -29,37 +13,17 @@ Object.extend(String, {
});
Object.extend(String.prototype, (function() {
function prepareReplacement(replacement) {
if (Object.isFunction(replacement)) return replacement;
var template = new Template(replacement);
return function(match) { return template.evaluate(match) };
}
/**
* String#gsub(pattern, replacement) -> String
*
* Returns the string with every occurence of a given pattern replaced by either
* a regular string, the returned value of a function or a [[Template]] string.
* 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;
replacement = prepareReplacement(replacement);
if (Object.isString(pattern))
pattern = RegExp.escape(pattern);
if (!(pattern.length || pattern.source)) {
replacement = replacement('');
return replacement + source.split('').join(replacement) + replacement;
}
while (source.length > 0) {
if (match = source.match(pattern)) {
result += source.slice(0, match.index);
@ -72,18 +36,6 @@ Object.extend(String.prototype, (function() {
return result;
}
/**
* String#sub(pattern, replacement[, count = 1]) -> String
*
* 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);
count = Object.isUndefined(count) ? 1 : count;
@ -94,176 +46,64 @@ Object.extend(String.prototype, (function() {
});
}
/** related to: String#gsub
* String#scan(pattern, iterator) -> String
*
* Allows iterating over every occurrence of the given pattern (which can be a
* string or a regular expression).
* Returns the original string.
**/
function scan(pattern, iterator) {
this.gsub(pattern, iterator);
return String(this);
}
/**
* String#truncate([length = 30[, suffix = '...']]) -> String
*
* Truncates a string to given `length` and appends `suffix` to it (indicating
* that it is only an excerpt).
**/
function truncate(length, truncation) {
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
return this.length > length ?
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : String(this);
}
/**
* String#strip() -> String
*
* Strips all leading and trailing whitespace from a string.
**/
function strip() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
}
/**
* String#stripTags() -> String
*
* Strips a string of any HTML tags.
*
* Note that `stripTags` will only strip HTML 4.01 tags &mdash; like `div`,
* `span`, and `abbr`. It _will not_ strip namespace-prefixed tags such
* as `h:table` or `xsl:template`.
*
* <h5>Caveat User</h5>
*
* Note that the processing `stripTags` does is good enough for most purposes, but
* you cannot rely on it for security purposes. If you're processing end-user-supplied
* content, `stripTags` is _not_ sufficiently robust to ensure that the content
* is completely devoid of HTML tags in the case of a user intentionally trying to circumvent
* tag restrictions. But then, you'll be running them through [[String#escapeHTML]] anyway,
* won't you?
**/
function stripTags() {
return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
return this.replace(/<\/?[^>]+>/gi, '');
}
/**
* String#stripScripts() -> String
*
* Strips a string of things that look like an HTML script blocks.
*
* <h5>Example</h5>
*
* "<p>This is a test.<script>alert("Look, a test!");</script>End of test</p>".stripScripts();
* // => "<p>This is a test.End of test</p>"
*
* <h5>Caveat User</h5>
*
* Note that the processing `stripScripts` does is good enough for most purposes,
* but you cannot rely on it for security purposes. If you're processing end-user-supplied
* content, `stripScripts` is probably not sufficiently robust to prevent hack attacks.
**/
function stripScripts() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
}
/**
* String#extractScripts() -> Array
*
* Extracts the content of any script blocks present in the string and
* returns them as an array of strings.
**/
function extractScripts() {
var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
matchOne = new RegExp(Prototype.ScriptFragment, 'im');
var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
}
/**
* String#evalScripts() -> Array
*
* Evaluates the content of any inline `<script>` block present in the string.
* Returns an array containing the value returned by each script.
* `<script>` blocks referencing external files will be treated as though
* they were empty (the result for that position in the array will be `undefined`);
* external files are _not_ loaded and processed by `evalScripts`.
*
* <h5>About `evalScripts`, `var`s, and defining functions</h5>
*
* `evalScripts` evaluates script blocks, but this **does not** mean they are
* evaluated in the global scope. They aren't, they're evaluated in the scope of
* the `evalScripts` method. This has important ramifications for your scripts:
*
* * Anything in your script declared with the `var` keyword will be
* discarded momentarily after evaluation, and will be invisible to any
* other scope.
* * If any `<script>` blocks _define functions_, they will need to be assigned to
* properties of the `window` object.
*
* For example, this won't work:
*
* // This kind of script won't work if processed by evalScripts:
* function coolFunc() {
* // Amazing stuff!
* }
*
* Instead, use the following syntax:
*
* // This kind of script WILL work if processed by evalScripts:
* window.coolFunc = function() {
* // Amazing stuff!
* }
*
* (You can leave off the `window.` part of that, but it's bad form.)
**/
function evalScripts() {
return this.extractScripts().map(function(script) { return eval(script) });
}
/**
* String#escapeHTML() -> String
*
* Converts HTML special characters to their entity equivalents.
**/
function escapeHTML() {
return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
var self = arguments.callee;
self.text.data = this;
return self.div.innerHTML;
}
/** related to: String#escapeHTML
* String#unescapeHTML() -> String
*
* Strips tags and converts the entity forms of special HTML characters
* to their normal form.
**/
function unescapeHTML() {
// Warning: In 1.7 String#unescapeHTML will no longer call String#stripTags.
return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
var div = new Element('div');
div.innerHTML = this.stripTags();
return div.childNodes[0] ? (div.childNodes.length > 1 ?
$A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
div.childNodes[0].nodeValue) : '';
}
/**
* String#parseQuery([separator = '&']) -> Object
**/
/** alias of: String#parseQuery, related to: Hash#toQueryString
* String#toQueryParams([separator = '&']) -> Object
*
* Parses a URI-like query string and returns an object composed of
* parameter/value pairs.
**/
function toQueryParams(separator) {
var match = this.strip().match(/([^?#]*)(#.*)?$/);
if (!match) return { };
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
var key = decodeURIComponent(pair.shift()),
value = pair.length > 1 ? pair.join('=') : pair[0];
var key = decodeURIComponent(pair.shift());
var value = pair.length > 1 ? pair.join('=') : pair[0];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
@ -276,131 +116,62 @@ Object.extend(String.prototype, (function() {
});
}
/**
* String#toArray() -> Array
*
* Splits the string character-by-character and returns an array with
* the result.
**/
function toArray() {
return this.split('');
}
/**
* String#succ() -> String
*
* Used internally by ObjectRange.
* Converts the last character of the string to the following character in
* the Unicode alphabet.
**/
function succ() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
}
/**
* String#times(count) -> String
*
* Concatenates the string `count` times.
**/
function times(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
}
/**
* String#camelize() -> String
*
* Converts a string separated by dashes into a camelCase equivalent.
* For instance, 'foo-bar' would be converted to 'fooBar'.
*
* <h5>Examples</h5>
*
* 'background-color'.camelize();
* // -> 'backgroundColor'
*
* '-moz-binding'.camelize();
* // -> 'MozBinding'
**/
function camelize() {
return this.replace(/-+(.)?/g, function(match, chr) {
return chr ? chr.toUpperCase() : '';
});
var parts = this.split('-'), len = parts.length;
if (len == 1) return parts[0];
var camelized = this.charAt(0) == '-'
? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
: parts[0];
for (var i = 1; i < len; i++)
camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
return camelized;
}
/**
* String#capitalize() -> String
*
* Capitalizes the first letter of a string and downcases all the others.
**/
function capitalize() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
}
/**
* String#underscore() -> String
*
* Converts a camelized string into a series of words separated by an
* underscore (`_`).
**/
function underscore() {
return this.replace(/::/g, '/')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
.replace(/-/g, '_')
.toLowerCase();
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
}
/**
* String#dasherize() -> String
*
* Replaces every instance of the underscore character ("_") by a dash ("-").
**/
function dasherize() {
return this.replace(/_/g, '-');
return this.gsub(/_/,'-');
}
/** related to: Object.inspect
* String#inspect([useDoubleQuotes = false]) -> String
*
* Returns a debug-oriented version of the string (i.e. wrapped in single or
* double quotes, with backslashes and quotes escaped).
**/
function inspect(useDoubleQuotes) {
var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
if (character in String.specialChar) {
return String.specialChar[character];
}
return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
var character = String.specialChar[match[0]];
return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
});
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
}
/** related to: Object.toJSON
* String#toJSON() -> String
*
* Returns a JSON string.
**/
function toJSON() {
return this.inspect(true);
}
/**
* String#unfilterJSON([filter = Prototype.JSONFilter]) -> String
*
* Strips comment delimiters around Ajax JSON or JavaScript responses.
* This security method is called internally.
**/
function unfilterJSON(filter) {
return this.replace(filter || Prototype.JSONFilter, '$1');
return this.sub(filter || Prototype.JSONFilter, '#{1}');
}
/**
* String#isJSON() -> Boolean
*
* Check if the string is valid JSON by the use of regular expressions.
* This security method is called internally.
**/
function isJSON() {
var str = this;
if (str.blank()) return false;
@ -408,15 +179,6 @@ Object.extend(String.prototype, (function() {
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
}
/**
* String#evalJSON([sanitize = false]) -> object
*
* Evaluates the JSON in the string and returns the resulting object.
*
* If the optional `sanitize` parameter is set to `true`, the string is
* checked for possible malicious attempts; if one is detected, `eval`
* is _not called_.
**/
function evalJSON(sanitize) {
var json = this.unfilterJSON();
try {
@ -425,75 +187,37 @@ Object.extend(String.prototype, (function() {
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
}
/**
* String#include(substring) -> Boolean
*
* Checks if the string contains `substring`.
**/
function include(pattern) {
return this.indexOf(pattern) > -1;
}
/**
* String#startsWith(substring) -> Boolean
*
* Checks if the string starts with `substring`.
**/
function startsWith(pattern) {
// We use `lastIndexOf` instead of `indexOf` to avoid tying execution
// time to string length when string doesn't start with pattern.
return this.lastIndexOf(pattern, 0) === 0;
return this.indexOf(pattern) === 0;
}
/**
* String#endsWith(substring) -> Boolean
*
* Checks if the string ends with `substring`.
**/
function endsWith(pattern) {
var d = this.length - pattern.length;
// We use `indexOf` instead of `lastIndexOf` to avoid tying execution
// time to string length when string doesn't end with pattern.
return d >= 0 && this.indexOf(pattern, d) === d;
return d >= 0 && this.lastIndexOf(pattern) === d;
}
/**
* String#empty() -> Boolean
*
* Checks if the string is empty.
**/
function empty() {
return this == '';
}
/**
* String#blank() -> Boolean
*
* Check if the string is "blank" &mdash; either empty (length of `0`) or containing
* only whitespace.
**/
function blank() {
return /^\s*$/.test(this);
}
/**
* String#interpolate(object[, pattern]) -> String
*
* Treats the string as a [[Template]] and fills it with `object`'s
* properties.
**/
function interpolate(object, pattern) {
return new Template(this, pattern).evaluate(object);
}
return {
gsub: gsub,
sub: sub,
scan: scan,
truncate: truncate,
// Firefox 3.5+ supports String.prototype.trim
// (`trim` is ~ 5x faster than `strip` in FF3.5)
strip: String.prototype.trim || strip,
strip: strip,
stripTags: stripTags,
stripScripts: stripScripts,
extractScripts: extractScripts,
@ -523,3 +247,19 @@ Object.extend(String.prototype, (function() {
};
})());
if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
escapeHTML: function() {
return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
},
unescapeHTML: function() {
return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
}
});
Object.extend(String.prototype.escapeHTML, {
div: document.createElement('div'),
text: document.createTextNode('')
});
String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

View File

@ -1,152 +1,32 @@
/** section: Language
* class Template
*
* A class for sophisticated string interpolation.
*
* Any time you have a group of similar objects and you need to produce
* formatted output for these objects, maybe inside a loop, you typically
* resort to concatenating string literals with the object's fields:
*
* "The TV show " + title + " was created by " + author + ".";
*
* There's nothing wrong with this approach, except that it is hard to
* visualize the output immediately just by glancing at the concatenation
* expression. The `Template` class provides a much nicer and clearer way of
* achieving this formatting.
*
* <h5>Straightforward templates</h5>
*
* The `Template` class uses a basic formatting syntax, similar to what is
* used in Ruby. The templates are created from strings that have embedded
* symbols in the form (e.g., `#{fieldName}`) that will be replaced by
* actual values when the template is applied (evaluated) to an object.
*
* // the template (our formatting expression)
* var myTemplate = new Template(
* 'The TV show #{title} was created by #{author}.');
*
* // our data to be formatted by the template
* var show = {
* title: 'The Simpsons',
* author: 'Matt Groening',
* network: 'FOX'
* };
*
* // let's format our data
* myTemplate.evaluate(show);
* // -> "The TV show The Simpsons was created by Matt Groening."
*
* <h5>Templates are meant to be reused</h5>
*
* As the example illustrates, `Template` objects are not tied to specific
* data. The data is bound to the template only during the evaluation of the
* template, without affecting the template itself. The next example shows the
* same template being used with a handful of distinct objects.
*
* // creating a few similar objects
* var conversion1 = { from: 'meters', to: 'feet', factor: 3.28 };
* var conversion2 = { from: 'kilojoules', to: 'BTUs', factor: 0.9478 };
* var conversion3 = { from: 'megabytes', to: 'gigabytes', factor: 1024 };
*
* // the template
* var templ = new Template(
* 'Multiply by #{factor} to convert from #{from} to #{to}.');
*
* // let's format each object
* [conversion1, conversion2, conversion3].each( function(conv){
* templ.evaluate(conv);
* });
* // -> Multiply by 3.28 to convert from meters to feet.
* // -> Multiply by 0.9478 to convert from kilojoules to BTUs.
* // -> Multiply by 1024 to convert from megabytes to gigabytes.
*
* <h5>Escape sequence</h5>
*
* There's always the chance that one day you'll need to have a literal in your
* template that looks like a symbol, but is not supposed to be replaced. For
* these situations there's an escape character: the backslash (<code>\\</code>).
*
* // NOTE: you're seeing two backslashes here because the backslash
* // is also an escape character in JavaScript strings, so a literal
* // backslash is represented by two backslashes.
* var t = new Template(
* 'in #{lang} we also use the \\#{variable} syntax for templates.');
* var data = { lang:'Ruby', variable: '(not used)' };
* t.evaluate(data);
* // -> in Ruby we also use the #{variable} syntax for templates.
*
* <h5>Custom syntaxes</h5>
*
* The default syntax of the template strings will probably be enough for most
* scenarios. In the rare occasion where the default Ruby-like syntax is
* 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 now ubiquitous `{{ }}`
* constructs:
*
* // matches symbols like '{{ field }}'
* var syntax = /(^|.|\r|\n)(\{{\s*(\w+)\s*}})/;
*
* var t = new Template(
* '<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>
*
* There are important constraints to any custom syntax. Any syntax must
* provide at least three groupings in the regular expression. The first
* grouping is to capture what comes before the symbol, to detect the backslash
* escape character (no, you cannot use a different character). The second
* grouping captures the entire symbol and will be completely replaced upon
* evaluation. Lastly, the third required grouping captures the name of the
* field inside the symbol.
*
**/
var Template = Class.create({
/**
* new Template(template[, pattern = Template.Pattern])
*
* Creates a Template object.
*
* The optional `pattern` argument expects a `RegExp` that defines a custom
* syntax for the replaceable symbols in `template`.
**/
initialize: function(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
/**
* Template#evaluate(object) -> String
*
* Applies the template to `object`'s data, producing a formatted string
* with symbols replaced by `object`'s corresponding properties.
**/
evaluate: function(object) {
if (object && Object.isFunction(object.toTemplateReplacements))
if (Object.isFunction(object.toTemplateReplacements))
object = object.toTemplateReplacements();
return this.template.gsub(this.pattern, function(match) {
if (object == null) return (match[1] + '');
if (object == null) return '';
var before = match[1] || '';
if (before == '\\') return match[2];
var ctx = object, expr = match[3],
pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
var ctx = object, expr = match[3];
var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
match = pattern.exec(expr);
if (match == null) return before;
while (match != null) {
var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
ctx = ctx[comp];
if (null == ctx || '' == match[3]) break;
expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
match = pattern.exec(expr);
}
return before + String.interpret(ctx);
});
}

102
src/prototype.js vendored
View File

@ -1,66 +1,72 @@
/* Prototype JavaScript framework, version <%= PROTOTYPE_VERSION %>
* (c) 2005-2009 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
*
*--------------------------------------------------------------------------*/
<%= include 'HEADER' %>
var Prototype = {
Version: '<%= PROTOTYPE_VERSION %>',
Browser: (function(){
var ua = navigator.userAgent;
// Opera (at least) 8.x+ has "Opera" as a [[Class]] of `window.opera`
// This is a safer inference than plain boolean type conversion of `window.opera`
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
return {
IE: !!window.attachEvent && !isOpera,
Opera: isOpera,
WebKit: ua.indexOf('AppleWebKit/') > -1,
Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
MobileSafari: /Apple.*Mobile.*Safari/.test(ua)
}
})(),
Browser: {
IE: !!(window.attachEvent &&
navigator.userAgent.indexOf('Opera') === -1),
Opera: navigator.userAgent.indexOf('Opera') > -1,
WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
Gecko: navigator.userAgent.indexOf('Gecko') > -1 &&
navigator.userAgent.indexOf('KHTML') === -1,
MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
},
BrowserFeatures: {
XPath: !!document.evaluate,
SelectorsAPI: !!document.querySelector,
ElementExtensions: (function() {
var constructor = window.Element || window.HTMLElement;
return !!(constructor && constructor.prototype);
})(),
SpecificElementExtensions: (function() {
// First, try the named class
if (typeof window.HTMLDivElement !== 'undefined')
return true;
var div = document.createElement('div'),
form = document.createElement('form'),
isSupported = false;
if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
isSupported = true;
}
div = form = null;
return isSupported;
})()
ElementExtensions: !!window.HTMLElement,
SpecificElementExtensions:
document.createElement('div')['__proto__'] &&
document.createElement('div')['__proto__'] !==
document.createElement('form')['__proto__']
},
ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
emptyFunction: function() { },
K: function(x) { return x }
};
if (Prototype.Browser.MobileSafari)
Prototype.BrowserFeatures.SpecificElementExtensions = false;
var Abstract = { };
//= require "lang"
//= require "ajax"
//= require "dom"
var Try = {
these: function() {
var returnValue;
for (var i = 0, length = arguments.length; i < length; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
break;
} catch (e) { }
}
return returnValue;
}
};
//= require "deprecated"
<%= include 'lang/class.js', 'lang/object.js', 'lang/function.js' %>
<%= include 'lang/date.js', 'lang/regexp.js', 'lang/periodical_executer.js' %>
<%= include 'lang/string.js', 'lang/template.js' %>
<%= include 'lang/enumerable.js', 'lang/array.js', 'lang/hash.js' %>
<%= include 'lang/number.js', 'lang/range.js' %>
<%= include 'ajax/ajax.js', 'ajax/responders.js', 'ajax/base.js', 'ajax/request.js', 'ajax/response.js' %>
<%= include 'ajax/updater.js', 'ajax/periodical_updater.js' %>
<%= include 'dom/dom.js', 'dom/selector.js', 'dom/form.js', 'dom/event.js' %>
<%= include 'deprecated.js' %>
Element.addMethods();

View File

@ -1,29 +0,0 @@
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;

File diff suppressed because it is too large Load Diff

View File

@ -225,38 +225,14 @@
})
</script>
<p id="mouseenter"><code>mouseenter</code> test</p>
<script type="text/javascript">
$('mouseenter').observe('click', function() {
$('mouseenter').insert("<br />carefully mouse over the word 'mouseenter', then outside of the box entirely");
$('mouseenter').observe('mouseenter', function(event) {
this.failed();
$('mouseenter').stopObserving();
});
$('mouseenter').observe('mouseleave', function(event) {
if ($(event.relatedTarget).descendantOf($('mouseenter'))) {
$('mouseenter').failed();
} else $('mouseenter').passed();
$('mouseenter').stopObserving();
});
});
</script>
<p id="addunload">Add unload events</p>
<script type="text/javascript">
$('addunload').observe('click', function(e){
if (this._done) return;
if (this._done) return
window.onunload = function(){ alert('inline unload fired!') }
Event.observe(window, 'unload', function(event){
if (!event.target) {
alert('FAILURE: event.target should not be null!');
}
alert('observed unload fired!')
});
Event.observe(window, 'unload', function(){ alert('observed unload fired!') })
this.update('Registered two unload events, one inline ("onunload") and one regular - try to refresh, both should fire')
this._done = true

50
test/lib/assets/test.css Normal file
View File

@ -0,0 +1,50 @@
body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li {
font-family: sans-serif;
}
body {
font-size:0.8em;
}
#log {
padding-bottom: 1em;
border-bottom: 2px solid #000;
margin-bottom: 2em;
}
.logsummary {
margin-top: 1em;
margin-bottom: 1em;
padding: 1ex;
border: 1px solid #000;
font-weight: bold;
}
.logtable {
width:100%;
border-collapse: collapse;
border: 1px dotted #666;
}
.logtable td, .logtable th {
text-align: left;
padding: 3px 8px;
border: 1px dotted #666;
}
.logtable .passed {
background-color: #cfc;
}
.logtable .failed, .logtable .error {
background-color: #fcc;
}
.logtable td div.action_buttons {
display: inline;
}
.logtable td div.action_buttons input {
margin: 0 5px;
font-size: 10px;
}

562
test/lib/assets/unittest.js Normal file
View File

@ -0,0 +1,562 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
var options = Object.extend({
pointerX: 0,
pointerY: 0,
buttons: 0
}, arguments[2] || {});
var oEvent = document.createEvent("MouseEvents");
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
false, false, false, false, 0, $(element));
if(this.mark) Element.remove(this.mark);
var style = 'position: absolute; width: 5px; height: 5px;' +
'top: #{pointerY}px; left: #{pointerX}px;'.interpolate(options) +
'border-top: 1px solid red; border-left: 1px solid red;'
this.mark = new Element('div', { style: style });
this.mark.appendChild(document.createTextNode(" "));
document.body.appendChild(this.mark);
if(this.step)
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
$(element).dispatchEvent(oEvent);
};
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
var options = Object.extend({
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, arguments[2] || {});
var oEvent = document.createEvent("KeyEvents");
oEvent.initKeyEvent(eventName, true, true, window,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.keyCode, options.charCode );
$(element).dispatchEvent(oEvent);
};
Event.simulateKeys = function(element, command) {
for(var i=0; i<command.length; i++) {
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
}
};
var Test = {
Unit: {
inspect: Object.inspect // security exception workaround
}
};
Test.Unit.Logger = Class.create({
initialize: function(element) {
this.element = $(element);
if (this.element) this._createLogTable();
this.tbody = $(this.element.getElementsByTagName('tbody')[0]);
},
start: function(testName) {
if (!this.element) return;
this.tbody.insert('<tr><td>' + testName + '</td><td></td><td></td></tr>');
},
setStatus: function(status) {
this.getLastLogLine().addClassName(status);
$(this.getLastLogLine().getElementsByTagName('td')[1]).update(status);
},
finish: function(status, summary) {
if (!this.element) return;
this.setStatus(status);
this.message(summary);
},
message: function(message) {
if (!this.element) return;
this.getMessageCell().update(this._toHTML(message));
},
summary: function(summary) {
if (!this.element) return;
var div = $(this.element.getElementsByTagName('div')[0]);
div.update(this._toHTML(summary));
},
getLastLogLine: function() {
//return this.element.descendants('tr').last();
var trs = this.element.getElementsByTagName('tr');
return $(trs[trs.length - 1]);
},
getMessageCell: function() {
return this.getLastLogLine().down('td', 2);
var tds = this.getLastLogLine().getElementsByTagName('td');
return $(tds[2]);
},
_createLogTable: function() {
var html = '<div class="logsummary">running...</div>' +
'<table class="logtable">' +
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
'<tbody class="loglines"></tbody>' +
'</table>';
this.element.update(html);
},
appendActionButtons: function(actions) {
actions = $H(actions);
if (!actions.any()) return;
var div = new Element("div", {className: 'action_buttons'});
actions.inject(div, function(container, action) {
var button = new Element("input").setValue(action.key).observe("click", action.value);
button.type = "button";
return container.insert(button);
});
this.getMessageCell().insert(div);
},
_toHTML: function(txt) {
return txt.escapeHTML().replace(/\n/g,"<br />");
}
});
Test.Unit.Runner = Class.create({
initialize: function(testcases) {
var options = this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
options.resultsURL = this.queryParams.resultsURL;
this.tests = this.getTests(testcases);
this.currentTest = 0;
Event.observe(window, "load", function() {
this.logger = new Test.Unit.Logger($(options.testLog));
this.runTests.bind(this).delay(0.1);
}.bind(this));
},
queryParams: window.location.search.parseQuery(),
getTests: function(testcases) {
var tests, options = this.options;
if (this.queryParams.tests) tests = this.queryParams.tests.split(',');
else if (options.tests) tests = options.tests;
else if (options.test) tests = [option.test];
else tests = Object.keys(testcases).grep(/^test/);
return tests.map(function(test) {
if (testcases[test])
return new Test.Unit.Testcase(test, testcases[test], testcases.setup, testcases.teardown);
}).compact();
},
getResult: function() {
var results = {
tests: this.tests.length,
assertions: 0,
failures: 0,
errors: 0
};
return this.tests.inject(results, function(results, test) {
results.assertions += test.assertions;
results.failures += test.failures;
results.errors += test.errors;
return results;
});
},
postResults: function() {
if (this.options.resultsURL) {
new Ajax.Request(this.options.resultsURL,
{ method: 'get', parameters: this.getResult(), asynchronous: false });
}
},
runTests: function() {
var test = this.tests[this.currentTest], actions;
if (!test) return this.finish();
if (!test.isWaiting) this.logger.start(test.name);
test.run();
if(test.isWaiting) {
this.logger.message("Waiting for " + test.timeToWait + "ms");
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
return;
}
this.logger.finish(test.status(), test.summary());
if (actions = test.actions) this.logger.appendActionButtons(actions);
this.currentTest++;
// tail recursive, hopefully the browser will skip the stackframe
this.runTests();
},
finish: function() {
this.postResults();
this.logger.summary(this.summary());
},
summary: function() {
return '#{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors'
.interpolate(this.getResult());
}
});
Test.Unit.MessageTemplate = Class.create({
initialize: function(string) {
var parts = [];
(string || '').scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/, function(part) {
parts.push(part[0]);
});
this.parts = parts;
},
evaluate: function(params) {
return this.parts.map(function(part) {
return part == '?' ? Test.Unit.inspect(params.shift()) : part.replace(/\\\?/, '?');
}).join('');
}
});
Test.Unit.Assertions = {
buildMessage: function(message, template) {
var args = $A(arguments).slice(2);
return (message ? message + '\n' : '') + new Test.Unit.MessageTemplate(template).evaluate(args);
},
flunk: function(message) {
this.assertBlock(message || 'Flunked', function() { return false });
},
assertBlock: function(message, block) {
try {
block.call(this) ? this.pass() : this.fail(message);
} catch(e) { this.error(e) }
},
assert: function(expression, message) {
message = this.buildMessage(message || 'assert', 'got <?>', expression);
this.assertBlock(message, function() { return expression });
},
assertEqual: function(expected, actual, message) {
message = this.buildMessage(message || 'assertEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected == actual });
},
assertNotEqual: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected != actual });
},
assertEnumEqual: function(expected, actual, message) {
expected = $A(expected);
actual = $A(actual);
message = this.buildMessage(message || 'assertEnumEqual', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() {
return expected.length == actual.length && expected.zip(actual).all(function(pair) { return pair[0] == pair[1] });
});
},
assertEnumNotEqual: function(expected, actual, message) {
expected = $A(expected);
actual = $A(actual);
message = this.buildMessage(message || 'assertEnumNotEqual', '<?> was the same as <?>', expected, actual);
this.assertBlock(message, function() {
return expected.length != actual.length || expected.zip(actual).any(function(pair) { return pair[0] != pair[1] });
});
},
assertHashEqual: function(expected, actual, message) {
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
message = this.buildMessage(message || 'assertHashEqual', 'expected <?>, actual: <?>', expected, actual);
// from now we recursively zip & compare nested arrays
var block = function() {
return expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(Object.isArray) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
});
};
this.assertBlock(message, block);
},
assertHashNotEqual: function(expected, actual, message) {
expected = $H(expected);
actual = $H(actual);
var expected_array = expected.toArray().sort(), actual_array = actual.toArray().sort();
message = this.buildMessage(message || 'assertHashNotEqual', '<?> was the same as <?>', expected, actual);
// from now we recursively zip & compare nested arrays
var block = function() {
return !(expected_array.length == actual_array.length &&
expected_array.zip(actual_array).all(function(pair) {
return pair.all(Object.isArray) ?
pair[0].zip(pair[1]).all(arguments.callee) : pair[0] == pair[1];
}));
};
this.assertBlock(message, block);
},
assertIdentical: function(expected, actual, message) {
message = this.buildMessage(message || 'assertIdentical', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected === actual });
},
assertNotIdentical: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotIdentical', 'expected <?>, actual: <?>', expected, actual);
this.assertBlock(message, function() { return expected !== actual });
},
assertNull: function(obj, message) {
message = this.buildMessage(message || 'assertNull', 'got <?>', obj);
this.assertBlock(message, function() { return obj === null });
},
assertNotNull: function(obj, message) {
message = this.buildMessage(message || 'assertNotNull', 'got <?>', obj);
this.assertBlock(message, function() { return obj !== null });
},
assertUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return typeof obj == "undefined" });
},
assertNotUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNotUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return typeof obj != "undefined" });
},
assertNullOrUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNullOrUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return obj == null });
},
assertNotNullOrUndefined: function(obj, message) {
message = this.buildMessage(message || 'assertNotNullOrUndefined', 'got <?>', obj);
this.assertBlock(message, function() { return obj != null });
},
assertMatch: function(expected, actual, message) {
message = this.buildMessage(message || 'assertMatch', 'regex <?> did not match <?>', expected, actual);
this.assertBlock(message, function() { return new RegExp(expected).exec(actual) });
},
assertNoMatch: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNoMatch', 'regex <?> matched <?>', expected, actual);
this.assertBlock(message, function() { return !(new RegExp(expected).exec(actual)) });
},
assertHidden: function(element, message) {
message = this.buildMessage(message || 'assertHidden', '? isn\'t hidden.', element);
this.assertBlock(message, function() { return element.style.display == 'none' });
},
assertInstanceOf: function(expected, actual, message) {
message = this.buildMessage(message || 'assertInstanceOf', '<?> was not an instance of the expected type', actual);
this.assertBlock(message, function() { return actual instanceof expected });
},
assertNotInstanceOf: function(expected, actual, message) {
message = this.buildMessage(message || 'assertNotInstanceOf', '<?> was an instance of the expected type', actual);
this.assertBlock(message, function() { return !(actual instanceof expected) });
},
assertRespondsTo: function(method, obj, message) {
message = this.buildMessage(message || 'assertRespondsTo', 'object doesn\'t respond to <?>', method);
this.assertBlock(message, function() { return (method in obj && typeof obj[method] == 'function') });
},
assertRaise: function(exceptionName, method, message) {
message = this.buildMessage(message || 'assertRaise', '<?> exception expected but none was raised', exceptionName);
var block = function() {
try {
method();
return false;
} catch(e) {
if (e.name == exceptionName) return true;
else throw e;
}
};
this.assertBlock(message, block);
},
assertNothingRaised: function(method, message) {
try {
method();
this.assert(true, "Expected nothing to be thrown");
} catch(e) {
message = this.buildMessage(message || 'assertNothingRaised', '<?> was thrown when nothing was expected.', e);
this.flunk(message);
}
},
_isVisible: function(element) {
element = $(element);
if(!element.parentNode) return true;
this.assertNotNull(element);
if(element.style && Element.getStyle(element, 'display') == 'none')
return false;
return arguments.callee.call(this, element.parentNode);
},
assertVisible: function(element, message) {
message = this.buildMessage(message, '? was not visible.', element);
this.assertBlock(message, function() { return this._isVisible(element) });
},
assertNotVisible: function(element, message) {
message = this.buildMessage(message, '? was not hidden and didn\'t have a hidden parent either.', element);
this.assertBlock(message, function() { return !this._isVisible(element) });
},
assertElementsMatch: function() {
var message, pass = true, expressions = $A(arguments), elements = $A(expressions.shift());
if (elements.length != expressions.length) {
message = this.buildMessage('assertElementsMatch', 'size mismatch: ? elements, ? expressions (?).', elements.length, expressions.length, expressions);
this.flunk(message);
pass = false;
}
elements.zip(expressions).all(function(pair, index) {
var element = $(pair.first()), expression = pair.last();
if (element.match(expression)) return true;
message = this.buildMessage('assertElementsMatch', 'In index <?>: expected <?> but got ?', index, expression, element);
this.flunk(message);
pass = false;
}.bind(this))
if (pass) this.assert(true, "Expected all elements to match.");
},
assertElementMatches: function(element, expression, message) {
this.assertElementsMatch([element], expression);
}
};
Test.Unit.Testcase = Class.create(Test.Unit.Assertions, {
initialize: function(name, test, setup, teardown) {
this.name = name;
this.test = test || Prototype.emptyFunction;
this.setup = setup || Prototype.emptyFunction;
this.teardown = teardown || Prototype.emptyFunction;
this.messages = [];
this.actions = {};
},
isWaiting: false,
timeToWait: 1000,
assertions: 0,
failures: 0,
errors: 0,
isRunningFromRake: window.location.port == 4711,
wait: function(time, nextPart) {
this.isWaiting = true;
this.test = nextPart;
this.timeToWait = time;
},
run: function(rethrow) {
try {
try {
if (!this.isWaiting) this.setup();
this.isWaiting = false;
this.test();
} finally {
if(!this.isWaiting) {
this.teardown();
}
}
}
catch(e) {
if (rethrow) throw e;
this.error(e, this);
}
},
summary: function() {
var msg = '#{assertions} assertions, #{failures} failures, #{errors} errors\n';
return msg.interpolate(this) + this.messages.join("\n");
},
pass: function() {
this.assertions++;
},
fail: function(message) {
this.failures++;
var line = "";
try {
throw new Error("stack");
} catch(e){
line = (/\.html:(\d+)/.exec(e.stack || '') || ['',''])[1];
}
this.messages.push("Failure: " + message + (line ? " Line #" + line : ""));
},
info: function(message) {
this.messages.push("Info: " + message);
},
error: function(error, test) {
this.errors++;
this.actions['retry with throw'] = function() { test.run(true) };
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) + ")");
},
status: function() {
if (this.failures > 0) return 'failed';
if (this.errors > 0) return 'error';
return 'passed';
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
}
});

504
test/lib/jstest.rb Normal file
View File

@ -0,0 +1,504 @@
require 'rake/tasklib'
require 'thread'
require 'webrick'
require 'fileutils'
include FileUtils
require 'erb'
class Browser
def supported?; true; end
def setup ; end
def open(url) ; end
def teardown ; end
def host
require 'rbconfig'
Config::CONFIG['host']
end
def macos?
host.include?('darwin')
end
def windows?
host.include?('mswin')
end
def linux?
host.include?('linux')
end
def applescript(script)
raise "Can't run AppleScript on #{host}" unless macos?
system "osascript -e '#{script}' 2>&1 >/dev/null"
end
end
class FirefoxBrowser < Browser
def initialize(path=File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe'))
@path = path
end
def visit(url)
system("open -a Firefox '#{url}'") if macos?
system("#{@path} #{url}") if windows?
system("firefox #{url}") if linux?
end
def to_s
"Firefox"
end
end
class ChromeBrowser < Browser
def initialize(path = nil)
@path = path || File.join(
ENV['UserPath'] || "C:/Documents and Settings/Administrator",
"Local Settings",
"Application Data",
"Google",
"Chrome",
"Application",
"chrome.exe"
)
end
def supported?
windows?
end
def visit(url)
system("#{@path} #{url}")
end
def to_s
"Chrome"
end
end
class SafariBrowser < Browser
def supported?
macos?
end
def setup
applescript('tell application "Safari" to make new document')
end
def visit(url)
applescript('tell application "Safari" to set URL of front document to "' + url + '"')
end
def teardown
#applescript('tell application "Safari" to close front document')
end
def to_s
"Safari"
end
end
class IEBrowser < Browser
def setup
require 'win32ole' if windows?
end
def supported?
windows?
end
def visit(url)
if windows?
ie = WIN32OLE.new('InternetExplorer.Application')
ie.visible = true
ie.Navigate(url)
sleep 0.01 while ie.Busy || ie.ReadyState != 4
end
end
def to_s
"Internet Explorer"
end
end
class KonquerorBrowser < Browser
@@configDir = File.join((ENV['HOME'] || ''), '.kde', 'share', 'config')
@@globalConfig = File.join(@@configDir, 'kdeglobals')
@@konquerorConfig = File.join(@@configDir, 'konquerorrc')
def supported?
linux?
end
# Forces KDE's default browser to be Konqueror during the tests, and forces
# Konqueror to open external URL requests in new tabs instead of a new
# window.
def setup
cd @@configDir, :verbose => false do
copy @@globalConfig, "#{@@globalConfig}.bak", :preserve => true, :verbose => false
copy @@konquerorConfig, "#{@@konquerorConfig}.bak", :preserve => true, :verbose => false
# Too lazy to write it in Ruby... Is sed dependency so bad?
system "sed -ri /^BrowserApplication=/d '#{@@globalConfig}'"
system "sed -ri /^KonquerorTabforExternalURL=/s:false:true: '#{@@konquerorConfig}'"
end
end
def teardown
cd @@configDir, :verbose => false do
copy "#{@@globalConfig}.bak", @@globalConfig, :preserve => true, :verbose => false
copy "#{@@konquerorConfig}.bak", @@konquerorConfig, :preserve => true, :verbose => false
end
end
def visit(url)
system("kfmclient openURL #{url}")
end
def to_s
"Konqueror"
end
end
class OperaBrowser < Browser
def initialize(path='c:\Program Files\Opera\Opera.exe')
@path = path
end
def setup
if windows?
puts %{
MAJOR ANNOYANCE on Windows.
You have to shut down Opera manually after each test
for the script to proceed.
Any suggestions on fixing this is GREATLY appreciated!
Thank you for your understanding.
}
end
end
def visit(url)
applescript('tell application "Opera" to GetURL "' + url + '"') if macos?
system("#{@path} #{url}") if windows?
system("opera #{url}") if linux?
end
def to_s
"Opera"
end
end
# shut up, webrick :-)
class ::WEBrick::HTTPServer
def access_log(config, req, res)
# nop
end
end
class ::WEBrick::BasicLog
def log(level, data)
# nop
end
end
class WEBrick::HTTPResponse
alias send send_response
def send_response(socket)
send(socket) unless fail_silently?
end
def fail_silently?
@fail_silently
end
def fail_silently
@fail_silently = true
end
end
class WEBrick::HTTPRequest
def to_json
headers = []
each { |k, v| headers.push "#{k.inspect}: #{v.inspect}" }
headers = "{" << headers.join(', ') << "}"
%({ "headers": #{headers}, "body": #{body.inspect}, "method": #{request_method.inspect} })
end
end
class WEBrick::HTTPServlet::AbstractServlet
def prevent_caching(res)
res['ETag'] = nil
res['Last-Modified'] = Time.now + 100**4
res['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
res['Pragma'] = 'no-cache'
res['Expires'] = Time.now - 100**4
end
end
class BasicServlet < WEBrick::HTTPServlet::AbstractServlet
def do_GET(req, res)
prevent_caching(res)
res['Content-Type'] = "text/plain"
req.query.each do |k, v|
res[k] = v unless k == 'responseBody'
end
res.body = req.query["responseBody"]
raise WEBrick::HTTPStatus::OK
end
def do_POST(req, res)
do_GET(req, res)
end
end
class SlowServlet < BasicServlet
def do_GET(req, res)
sleep(2)
super
end
end
class DownServlet < BasicServlet
def do_GET(req, res)
res.fail_silently
end
end
class InspectionServlet < BasicServlet
def do_GET(req, res)
prevent_caching(res)
res['Content-Type'] = "application/json"
res.body = req.to_json
raise WEBrick::HTTPStatus::OK
end
end
class NonCachingFileHandler < WEBrick::HTTPServlet::FileHandler
def do_GET(req, res)
super
set_default_content_type(res, req.path)
prevent_caching(res)
end
def set_default_content_type(res, path)
res['Content-Type'] = case path
when /\.js$/ then 'text/javascript'
when /\.html$/ then 'text/html'
when /\.css$/ then 'text/css'
else 'text/plain'
end
end
end
class JavaScriptTestTask < ::Rake::TaskLib
def initialize(name=:test)
@name = name
@tests = []
@browsers = []
@queue = Queue.new
@server = WEBrick::HTTPServer.new(:Port => 4711) # TODO: make port configurable
@server.mount_proc("/results") do |req, res|
@queue.push(req)
res.body = "OK"
end
@server.mount("/response", BasicServlet)
@server.mount("/slow", SlowServlet)
@server.mount("/down", DownServlet)
@server.mount("/inspect", InspectionServlet)
yield self if block_given?
define
end
def define
task @name do
trap("INT") { @server.shutdown; exit }
t = Thread.new { @server.start }
# run all combinations of browsers and tests
@browsers.each do |browser|
if browser.supported?
t0 = Time.now
test_suite_results = TestSuiteResults.new
browser.setup
puts "\nStarted tests in #{browser}."
@tests.each do |test|
browser.visit(get_url(test))
results = TestResults.new(@queue.pop.query, test[:url])
print results
test_suite_results << results
end
print "\nFinished in #{Time.now - t0} seconds."
print test_suite_results
browser.teardown
else
puts "\nSkipping #{browser}, not supported on this OS."
end
end
@server.shutdown
t.join
end
end
def get_url(test)
params = "resultsURL=http://localhost:4711/results&t=" + ("%.6f" % Time.now.to_f)
params << "&tests=#{test[:testcases]}" unless test[:testcases] == :all
"http://localhost:4711#{test[:url]}?#{params}"
end
def mount(path, dir=nil)
dir = Dir.pwd + path unless dir
# don't cache anything in our tests
@server.mount(path, NonCachingFileHandler, dir)
end
# test should be specified as a hash of the form
# {:url => "url", :testcases => "testFoo,testBar"}.
# specifying :testcases is optional
def run(url, testcases = :all)
@tests << { :url => url, :testcases => testcases }
end
def browser(browser)
browser =
case(browser)
when :firefox
FirefoxBrowser.new
when :safari
SafariBrowser.new
when :ie
IEBrowser.new
when :konqueror
KonquerorBrowser.new
when :opera
OperaBrowser.new
when :chrome
ChromeBrowser.new
else
browser
end
@browsers<<browser
end
end
class TestResults
attr_reader :tests, :assertions, :failures, :errors, :filename
def initialize(query, filename)
@tests = query['tests'].to_i
@assertions = query['assertions'].to_i
@failures = query['failures'].to_i
@errors = query['errors'].to_i
@filename = filename
end
def error?
@errors > 0
end
def failure?
@failures > 0
end
def to_s
return "E" if error?
return "F" if failure?
"."
end
end
class TestSuiteResults
def initialize
@tests = 0
@assertions = 0
@failures = 0
@errors = 0
@error_files = []
@failure_files = []
end
def <<(result)
@tests += result.tests
@assertions += result.assertions
@failures += result.failures
@errors += result.errors
@error_files.push(result.filename) if result.error?
@failure_files.push(result.filename) if result.failure?
end
def error?
@errors > 0
end
def failure?
@failures > 0
end
def to_s
str = ""
str << "\n Failures: #{@failure_files.join(', ')}" if failure?
str << "\n Errors: #{@error_files.join(', ')}" if error?
"#{str}\n#{summary}\n\n"
end
def summary
"#{@tests} tests, #{@assertions} assertions, #{@failures} failures, #{@errors} errors."
end
end
class PageBuilder
UNITTEST_DIR = File.expand_path('test')
FIXTURES_DIR = File.join(UNITTEST_DIR, 'unit', 'fixtures')
TMP_DIR = File.join(UNITTEST_DIR, 'unit', 'tmp')
TEMPLATES_DIR = File.join(UNITTEST_DIR, 'lib', 'templates')
def initialize(filename, template = 'default.erb')
@filename = filename
@template = File.join(self.class::TEMPLATES_DIR, template)
@js_filename = File.basename(@filename)
@basename = @js_filename.sub('_test.js', '')
end
def html_fixtures
content = ""
file = File.join(FIXTURES_DIR, "#{@basename}.html")
File.open(file).each { |l| content << l } if File.exists?(file)
content
end
def external_fixtures(extension)
filename = "#{@basename}.#{extension}"
File.exists?(File.join(FIXTURES_DIR, filename)) ? filename : nil
end
def render
@title = @basename.gsub('_', ' ').strip.capitalize
@html_fixtures = html_fixtures
@js_fixtures_filename = external_fixtures('js')
@css_fixtures_filename = external_fixtures('css')
File.open(destination, 'w+') do |file|
file << ERB.new(IO.read(@template), nil, '%').result(binding)
end
end
def destination
name_file(:ext => 'html')
end
def name_file(options = {})
prefix = options[:prefix] ? "#{options[:prefix]}_" : ""
suffix = options[:suffix] ? "_#{options[:suffix]}" : ""
ext = options[:ext] ? options[:ext] : "js"
filename = File.basename(@filename, '.js')
File.join(TMP_DIR, "#{prefix}#{filename}#{suffix}.#{ext}")
end
end

View File

@ -0,0 +1,35 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Unit test file | <%= @title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="../../../dist/prototype.js" type="text/javascript"></script>
<script src="../../lib/assets/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../lib/assets/test.css" type="text/css" />
<% if @css_fixtures_filename %>
<link rel="stylesheet" href="../fixtures/<%= @css_fixtures_filename %>" type="text/css" charset="utf-8" />
<% end %>
<% if @js_fixtures_filename %>
<script src="../fixtures/<%= @js_fixtures_filename %>" type="text/javascript" charset="utf-8"></script>
<% end %>
<script src="../<%= @js_filename %>" type="text/javascript"></script>
</head>
<body>
<h1>Unit test file</h1>
<h2><%= @title %></h2>
<!-- This file is programmatically generated. Do not attempt to modify it. Instead, modify <%= @fixtures_filename %> -->
<!-- Log output start -->
<div id="testlog"></div>
<!-- Log output end -->
<!-- HTML Fixtures start -->
<%= @html_fixtures %>
<!-- HTML Fixtures end -->
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Prototype Unit test file | <%= @title %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" charset="utf-8">
var eventResults = {};
var originalElement = window.Element;
</script>
<script src="../../../dist/prototype.js" type="text/javascript"></script>
<script src="../../lib/assets/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../lib/assets/test.css" type="text/css" />
<% if @css_fixtures_filename %>
<link rel="stylesheet" href="../fixtures/<%= @css_fixtures_filename %>" type="text/css" charset="utf-8" />
<% end %>
<% if @js_fixtures_filename %>
<script src="../fixtures/<%= @js_fixtures_filename %>" type="text/javascript" charset="utf-8"></script>
<% end %>
<script src="../<%= @js_filename %>" type="text/javascript"></script>
</head>
<body>
<h1>Prototype Unit test file</h1>
<h2><%= @title %></h2>
<!-- This file is programmatically generated. Do not attempt to modify it. Instead, modify <%= @fixtures_filename %> -->
<!-- Log output start -->
<div id="testlog"></div>
<!-- Log output end -->
<!-- HTML Fixtures start -->
<%= @html_fixtures %>
<!-- HTML Fixtures end -->
</body>
</html>
<script type="text/javascript" charset="utf-8">
eventResults.endOfDocument = true;
</script>

View File

@ -96,7 +96,7 @@ new Test.Unit.Runner({
onComplete: Prototype.emptyFunction
}
var request = new Ajax.Updater("content", "../fixtures/hello.js", options);
request.options.onComplete = Prototype.emptyFunction;
request.options.onComplete = function() {};
this.assertIdentical(Prototype.emptyFunction, options.onComplete);
},
@ -105,7 +105,7 @@ new Test.Unit.Runner({
this.assertEqual(1, Ajax.Responders.responders.length);
var dummyResponder = {
onComplete: Prototype.emptyFunction
onComplete: function(req) { /* dummy */ }
};
Ajax.Responders.register(dummyResponder);
@ -376,4 +376,4 @@ new Test.Unit.Runner({
this.info(message);
}
}
});
});

View File

@ -33,16 +33,6 @@ new Test.Unit.Runner({
this.assertEqual(3, $A(element.childNodes).length);
},
testToArrayOnPrimitive: function() {
this.assertEnumEqual(['a', 'b', 'c'], $A('abc'));
this.assertEnumEqual([], $A(''));
this.assertEnumEqual([], $A(null));
this.assertEnumEqual([], $A(undefined));
this.assertEnumEqual([], $A());
this.assertEnumEqual([], $A(5));
this.assertEnumEqual([], $A(true));
},
testClear: function(){
this.assertEnumEqual([], [].clear());
this.assertEnumEqual([], [1].clear());
@ -147,6 +137,14 @@ new Test.Unit.Runner({
this.assertEqual('[\"a\", 1]', ['a', 1].toJSON());
this.assertEqual('[\"a\", {\"b\": null}]', ['a', {'b': null}].toJSON());
},
testReduce: function(){
this.assertUndefined([].reduce());
this.assertNull([null].reduce());
this.assertEqual(1, [1].reduce());
this.assertEnumEqual([1,2,3], [1,2,3].reduce());
this.assertEnumEqual([1,null,3], [1,null,3].reduce());
},
testReverse: function(){
this.assertEnumEqual([], [].reverse());
@ -187,10 +185,5 @@ new Test.Unit.Runner({
this.assertEnumEqual(['a'], $w('a '));
this.assertEnumEqual(['a'], $w(' a'));
this.assertEnumEqual(['a', 'b', 'c', 'd'], $w(' a b\nc\t\nd\n'));
},
testConcat: function(){
var args = (function() { return [].concat(arguments) })(1, 2);
this.assertIdentical(1, args[0][0]);
}
});

View File

@ -1,4 +1,354 @@
new Test.Unit.Runner({
testFunctionArgumentNames: function() {
this.assertEnumEqual([], (function() {}).argumentNames());
this.assertEnumEqual(["one"], (function(one) {}).argumentNames());
this.assertEnumEqual(["one", "two", "three"], (function(one, two, three) {}).argumentNames());
this.assertEnumEqual(["one", "two", "three"], (function( one , two
, three ) {}).argumentNames());
this.assertEqual("$super", (function($super) {}).argumentNames().first());
function named1() {};
this.assertEnumEqual([], named1.argumentNames());
function named2(one) {};
this.assertEnumEqual(["one"], named2.argumentNames());
function named3(one, two, three) {};
this.assertEnumEqual(["one", "two", "three"], named3.argumentNames());
},
testFunctionBind: function() {
function methodWithoutArguments() { return this.hi };
function methodWithArguments() { return this.hi + ',' + $A(arguments).join(',') };
var func = Prototype.emptyFunction;
this.assertIdentical(func, func.bind());
this.assertIdentical(func, func.bind(undefined));
this.assertNotIdentical(func, func.bind(null));
this.assertEqual('without', methodWithoutArguments.bind({ hi: 'without' })());
this.assertEqual('with,arg1,arg2', methodWithArguments.bind({ hi: 'with' })('arg1','arg2'));
this.assertEqual('withBindArgs,arg1,arg2',
methodWithArguments.bind({ hi: 'withBindArgs' }, 'arg1', 'arg2')());
this.assertEqual('withBindArgsAndArgs,arg1,arg2,arg3,arg4',
methodWithArguments.bind({ hi: 'withBindArgsAndArgs' }, 'arg1', 'arg2')('arg3', 'arg4'));
},
testFunctionCurry: function() {
var split = function(delimiter, string) { return string.split(delimiter); };
var splitOnColons = split.curry(":");
this.assertNotIdentical(split, splitOnColons);
this.assertEnumEqual(split(":", "0:1:2:3:4:5"), splitOnColons("0:1:2:3:4:5"));
this.assertIdentical(split, split.curry());
},
testFunctionDelay: function() {
window.delayed = undefined;
var delayedFunction = function() { window.delayed = true; };
var delayedFunctionWithArgs = function() { window.delayedWithArgs = $A(arguments).join(' '); };
delayedFunction.delay(0.8);
delayedFunctionWithArgs.delay(0.8, 'hello', 'world');
this.assertUndefined(window.delayed);
this.wait(1000, function() {
this.assert(window.delayed);
this.assertEqual('hello world', window.delayedWithArgs);
});
},
testFunctionWrap: function() {
function sayHello(){
return 'hello world';
};
this.assertEqual('HELLO WORLD', sayHello.wrap(function(proceed) {
return proceed().toUpperCase();
})());
var temp = String.prototype.capitalize;
String.prototype.capitalize = String.prototype.capitalize.wrap(function(proceed, eachWord) {
if(eachWord && this.include(' ')) return this.split(' ').map(function(str){
return str.capitalize();
}).join(' ');
return proceed();
});
this.assertEqual('Hello world', 'hello world'.capitalize());
this.assertEqual('Hello World', 'hello world'.capitalize(true));
this.assertEqual('Hello', 'hello'.capitalize());
String.prototype.capitalize = temp;
},
testFunctionDefer: function() {
window.deferred = undefined;
var deferredFunction = function() { window.deferred = true; };
deferredFunction.defer();
this.assertUndefined(window.deferred);
this.wait(50, function() {
this.assert(window.deferred);
window.deferredValue = 0;
var deferredFunction2 = function(arg) { window.deferredValue = arg; };
deferredFunction2.defer('test');
this.wait(50, function() {
this.assertEqual('test', window.deferredValue);
});
});
},
testFunctionMethodize: function() {
var Foo = { bar: function(baz) { return baz } };
var baz = { quux: Foo.bar.methodize() };
this.assertEqual(Foo.bar.methodize(), baz.quux);
this.assertEqual(baz, Foo.bar(baz));
this.assertEqual(baz, baz.quux());
},
testObjectExtend: function() {
var object = {foo: 'foo', bar: [1, 2, 3]};
this.assertIdentical(object, Object.extend(object));
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3]}, object);
this.assertIdentical(object, Object.extend(object, {bla: 123}));
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object);
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: null},
Object.extend(object, {bla: null}));
},
testObjectToQueryString: function() {
this.assertEqual('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'}));
},
testObjectClone: function() {
var object = {foo: 'foo', bar: [1, 2, 3]};
this.assertNotIdentical(object, Object.clone(object));
this.assertHashEqual(object, Object.clone(object));
this.assertHashEqual({}, Object.clone());
var clone = Object.clone(object);
delete clone.bar;
this.assertHashEqual({foo: 'foo'}, clone,
"Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted.");
},
testObjectInspect: function() {
this.assertEqual('undefined', Object.inspect());
this.assertEqual('undefined', Object.inspect(undefined));
this.assertEqual('null', Object.inspect(null));
this.assertEqual("'foo\\\\b\\\'ar'", Object.inspect('foo\\b\'ar'));
this.assertEqual('[]', Object.inspect([]));
this.assertNothingRaised(function() { Object.inspect(window.Node) });
},
testObjectToJSON: function() {
this.assertUndefined(Object.toJSON(undefined));
this.assertUndefined(Object.toJSON(Prototype.K));
this.assertEqual('\"\"', Object.toJSON(''));
this.assertEqual('[]', Object.toJSON([]));
this.assertEqual('[\"a\"]', Object.toJSON(['a']));
this.assertEqual('[\"a\", 1]', Object.toJSON(['a', 1]));
this.assertEqual('[\"a\", {\"b\": null}]', Object.toJSON(['a', {'b': null}]));
this.assertEqual('{\"a\": \"hello!\"}', Object.toJSON({a: 'hello!'}));
this.assertEqual('{}', Object.toJSON({}));
this.assertEqual('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K}));
this.assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}',
Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}));
this.assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}',
Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})));
this.assertEqual('true', Object.toJSON(true));
this.assertEqual('false', Object.toJSON(false));
this.assertEqual('null', Object.toJSON(null));
var sam = new Person('sam');
this.assertEqual('-sam', Object.toJSON(sam));
this.assertEqual('-sam', sam.toJSON());
var element = $('test');
this.assertUndefined(Object.toJSON(element));
element.toJSON = function(){return 'I\'m a div with id test'};
this.assertEqual('I\'m a div with id test', Object.toJSON(element));
},
testObjectToHTML: function() {
this.assertIdentical('', Object.toHTML());
this.assertIdentical('', Object.toHTML(''));
this.assertIdentical('', Object.toHTML(null));
this.assertIdentical('0', Object.toHTML(0));
this.assertIdentical('123', Object.toHTML(123));
this.assertEqual('hello world', Object.toHTML('hello world'));
this.assertEqual('hello world', Object.toHTML({toHTML: function() { return 'hello world' }}));
},
testObjectIsArray: function() {
this.assert(Object.isArray([]));
this.assert(Object.isArray([0]));
this.assert(Object.isArray([0, 1]));
this.assert(!Object.isArray({}));
this.assert(!Object.isArray($('list').childNodes));
this.assert(!Object.isArray());
this.assert(!Object.isArray(''));
this.assert(!Object.isArray('foo'));
this.assert(!Object.isArray(0));
this.assert(!Object.isArray(1));
this.assert(!Object.isArray(null));
this.assert(!Object.isArray(true));
this.assert(!Object.isArray(false));
this.assert(!Object.isArray(undefined));
},
testObjectIsHash: function() {
this.assert(Object.isHash($H()));
this.assert(Object.isHash(new Hash()));
this.assert(!Object.isHash({}));
this.assert(!Object.isHash(null));
this.assert(!Object.isHash());
this.assert(!Object.isHash(''));
this.assert(!Object.isHash(2));
this.assert(!Object.isHash(false));
this.assert(!Object.isHash(true));
this.assert(!Object.isHash([]));
},
testObjectIsElement: function() {
this.assert(Object.isElement(document.createElement('div')));
this.assert(Object.isElement(new Element('div')));
this.assert(Object.isElement($('testlog')));
this.assert(!Object.isElement(document.createTextNode('bla')));
// falsy variables should not mess up return value type
this.assertIdentical(false, Object.isElement(0));
this.assertIdentical(false, Object.isElement(''));
this.assertIdentical(false, Object.isElement(NaN));
this.assertIdentical(false, Object.isElement(null));
this.assertIdentical(false, Object.isElement(undefined));
},
testObjectIsFunction: function() {
this.assert(Object.isFunction(function() { }));
this.assert(Object.isFunction(Class.create()));
this.assert(!Object.isFunction("a string"));
this.assert(!Object.isFunction($("testlog")));
this.assert(!Object.isFunction([]));
this.assert(!Object.isFunction({}));
this.assert(!Object.isFunction(0));
this.assert(!Object.isFunction(false));
this.assert(!Object.isFunction(undefined));
},
testObjectIsString: function() {
this.assert(!Object.isString(function() { }));
this.assert(Object.isString("a string"));
this.assert(!Object.isString(0));
this.assert(!Object.isString([]));
this.assert(!Object.isString({}));
this.assert(!Object.isString(false));
this.assert(!Object.isString(undefined));
},
testObjectIsNumber: function() {
this.assert(Object.isNumber(0));
this.assert(Object.isNumber(1.0));
this.assert(!Object.isNumber(function() { }));
this.assert(!Object.isNumber("a string"));
this.assert(!Object.isNumber([]));
this.assert(!Object.isNumber({}));
this.assert(!Object.isNumber(false));
this.assert(!Object.isNumber(undefined));
},
testObjectIsUndefined: function() {
this.assert(Object.isUndefined(undefined));
this.assert(!Object.isUndefined(null));
this.assert(!Object.isUndefined(false));
this.assert(!Object.isUndefined(0));
this.assert(!Object.isUndefined(""));
this.assert(!Object.isUndefined(function() { }));
this.assert(!Object.isUndefined([]));
this.assert(!Object.isUndefined({}));
},
// sanity check
testDoesntExtendObjectPrototype: function() {
// for-in is supported with objects
var iterations = 0, obj = { a: 1, b: 2, c: 3 };
for(property in obj) iterations++;
this.assertEqual(3, iterations);
// for-in is not supported with arrays
iterations = 0;
var arr = [1,2,3];
for(property in arr) iterations++;
this.assert(iterations > 3);
},
testPeriodicalExecuterStop: function() {
var peEventCount = 0;
function peEventFired(pe) {
if (++peEventCount > 2) pe.stop();
}
// peEventFired will stop the PeriodicalExecuter after 3 callbacks
new PeriodicalExecuter(peEventFired, 0.05);
this.wait(600, function() {
this.assertEqual(3, peEventCount);
});
},
testBindAsEventListener: function() {
for( var i = 0; i < 10; ++i ){
var div = document.createElement('div');
div.setAttribute('id','test-'+i);
document.body.appendChild(div);
var tobj = new TestObj();
var eventTest = { test: true };
var call = tobj.assertingEventHandler.bindAsEventListener(tobj,
this.assertEqual.bind(this, eventTest),
this.assertEqual.bind(this, arg1),
this.assertEqual.bind(this, arg2),
this.assertEqual.bind(this, arg3), arg1, arg2, arg3 );
call(eventTest);
}
},
testDateToJSON: function() {
this.assertEqual('\"1970-01-01T00:00:00Z\"', new Date(Date.UTC(1970, 0, 1)).toJSON());
},
testRegExpEscape: function() {
this.assertEqual('word', RegExp.escape('word'));
this.assertEqual('\\/slashes\\/', RegExp.escape('/slashes/'));
this.assertEqual('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\'));
this.assertEqual('\\\\border of word', RegExp.escape('\\border of word'));
this.assertEqual('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)'));
this.assertEqual('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]);
this.assertEqual('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)'));
this.assertEqual('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]);
this.assertEqual('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)'));
this.assertEqual('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]);
this.assertEqual('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)'));
this.assertEqual('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]);
this.assertEqual('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?<!negative-lookbehind)'));
this.assertEqual('negative-lookbehind', new RegExp(RegExp.escape('(?<!') + '([^)]+)').exec('(?<!negative-lookbehind)')[1]);
this.assertEqual('\\[\\\\w\\]\\+', RegExp.escape('[\\w]+'));
this.assertEqual('character class', new RegExp(RegExp.escape('[') + '([^\\]]+)').exec('[character class]')[1]);
this.assertEqual('<div>', new RegExp(RegExp.escape('<div>')).exec('<td><div></td>')[0]);
this.assertEqual('false', RegExp.escape(false));
this.assertEqual('undefined', RegExp.escape());
this.assertEqual('null', RegExp.escape(null));
this.assertEqual('42', RegExp.escape(42));
this.assertEqual('\\\\n\\\\r\\\\t', RegExp.escape('\\n\\r\\t'));
this.assertEqual('\n\r\t', RegExp.escape('\n\r\t'));
this.assertEqual('\\{5,2\\}', RegExp.escape('{5,2}'));
this.assertEqual(
'\\/\\(\\[\\.\\*\\+\\?\\^\\=\\!\\:\\$\\{\\}\\(\\)\\|\\[\\\\\\]\\\\\\\/\\\\\\\\\\]\\)\\/g',
RegExp.escape('/([.*+?^=!:${}()|[\\]\\/\\\\])/g')
);
},
testBrowserDetection: function() {
var results = $H(Prototype.Browser).map(function(engine){
return engine;
@ -25,7 +375,7 @@ new Test.Unit.Runner({
this.assert(Prototype.Browser.WebKit);
}
if(Object.prototype.toString.call(window.opera) === '[object Opera]') {
if(!!window.opera) {
this.info('Running on Opera');
this.assert(Prototype.Browser.Opera);
}
@ -39,5 +389,134 @@ new Test.Unit.Runner({
this.info('Running on Gecko');
this.assert(Prototype.Browser.Gecko);
}
}
},
testClassCreate: function() {
this.assert(Object.isFunction(Animal), 'Animal is not a constructor');
this.assertEnumEqual([Cat, Mouse, Dog, Ox], Animal.subclasses);
Animal.subclasses.each(function(subclass) {
this.assertEqual(Animal, subclass.superclass);
}, this);
var Bird = Class.create(Animal);
this.assertEqual(Bird, Animal.subclasses.last());
// for..in loop (for some reason) doesn't iterate over the constructor property in top-level classes
this.assertEnumEqual(Object.keys(new Animal).sort(), Object.keys(new Bird).without('constructor').sort());
},
testClassInstantiation: function() {
var pet = new Animal("Nibbles");
this.assertEqual("Nibbles", pet.name, "property not initialized");
this.assertEqual('Nibbles: Hi!', pet.say('Hi!'));
this.assertEqual(Animal, pet.constructor, "bad constructor reference");
this.assertUndefined(pet.superclass);
var Empty = Class.create();
this.assert('object', typeof new Empty);
},
testInheritance: function() {
var tom = new Cat('Tom');
this.assertEqual(Cat, tom.constructor, "bad constructor reference");
this.assertEqual(Animal, tom.constructor.superclass, 'bad superclass reference');
this.assertEqual('Tom', tom.name);
this.assertEqual('Tom: meow', tom.say('meow'));
this.assertEqual('Tom: Yuk! I only eat mice.', tom.eat(new Animal));
},
testSuperclassMethodCall: function() {
var tom = new Cat('Tom');
this.assertEqual('Tom: Yum!', tom.eat(new Mouse));
// augment the constructor and test
var Dodo = Class.create(Animal, {
initialize: function($super, name) {
$super(name);
this.extinct = true;
},
say: function($super, message) {
return $super(message) + " honk honk";
}
});
var gonzo = new Dodo('Gonzo');
this.assertEqual('Gonzo', gonzo.name);
this.assert(gonzo.extinct, 'Dodo birds should be extinct');
this.assertEqual("Gonzo: hello honk honk", gonzo.say("hello"));
},
testClassAddMethods: function() {
var tom = new Cat('Tom');
var jerry = new Mouse('Jerry');
Animal.addMethods({
sleep: function() {
return this.say('ZZZ');
}
});
Mouse.addMethods({
sleep: function($super) {
return $super() + " ... no, can't sleep! Gotta steal cheese!";
},
escape: function(cat) {
return this.say('(from a mousehole) Take that, ' + cat.name + '!');
}
});
this.assertEqual('Tom: ZZZ', tom.sleep(), "added instance method not available to subclass");
this.assertEqual("Jerry: ZZZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep());
this.assertEqual("Jerry: (from a mousehole) Take that, Tom!", jerry.escape(tom));
// insure that a method has not propagated *up* the prototype chain:
this.assertUndefined(tom.escape);
this.assertUndefined(new Animal().escape);
Animal.addMethods({
sleep: function() {
return this.say('zZzZ');
}
});
this.assertEqual("Jerry: zZzZ ... no, can't sleep! Gotta steal cheese!", jerry.sleep());
},
testBaseClassWithMixin: function() {
var grass = new Plant('grass', 3);
this.assertRespondsTo('getValue', grass);
this.assertEqual('#<Plant: grass>', grass.inspect());
},
testSubclassWithMixin: function() {
var snoopy = new Dog('Snoopy', 12, 'male');
this.assertRespondsTo('reproduce', snoopy);
},
testSubclassWithMixins: function() {
var cow = new Ox('cow', 400, 'female');
this.assertEqual('#<Ox: cow>', cow.inspect());
this.assertRespondsTo('reproduce', cow);
this.assertRespondsTo('getValue', cow);
},
testClassWithToStringAndValueOfMethods: function() {
var Foo = Class.create({
toString: function() { return "toString" },
valueOf: function() { return "valueOf" }
});
var Parent = Class.create({
m1: function(){ return 'm1' },
m2: function(){ return 'm2' }
});
var Child = Class.create(Parent, {
m1: function($super) { return 'm1 child' },
m2: function($super) { return 'm2 child' }
});
this.assert(new Child().m1.toString().indexOf('m1 child') > -1);
this.assertEqual("toString", new Foo().toString());
this.assertEqual("valueOf", new Foo().valueOf());
}
});

View File

@ -1,53 +0,0 @@
{
"description": "Extends the default HTML attribute whiteList for Prototype testing purposes.",
"inherits": [
{
"src": "resource:///html4-attributes.json"
}
],
"allowed": [
"FORM:ACCEPT-CHARSET",
"DIV:FOO",
"DIV:BAR",
"A:ACCESSKEY"
],
"types": [
{
"key": "A:ACCESSKEY",
"description": "Allow the A:ACCESSKEY accessibility key character",
"pattern": ".?",
"optional": true
},
{
"key": "INPUT:TYPE",
"description": "Overwrite INPUT:TYPE rule to allow for DATE value.",
"pattern": "TEXT|PASSWORD|CHECKBOX|RADIO|SUBMIT|RESET|FILE|HIDDEN|IMAGE|BUTTON|DATE",
"optional": true
},
{
"key": "A:TABINDEX",
"description": "Overwrite A:TABINDEX to allow for an empty value",
"pattern": ".*",
"optional": true
},
{
"key": "DIV:FOO",
"description": "Custom BAR attribute.",
"pattern": ".*",
"optional": true
},
{
"key": "DIV:BAR",
"description": "Custom FOO attribute",
"pattern": ".*",
"optional": true
}
]
}

View File

@ -112,10 +112,6 @@ new Test.Unit.Runner({
toString: function() { return "toString" },
valueOf: function() { return "valueOf" }
});
var Bar = Class.create(Foo, {
valueOf: function() { return "myValueOf" }
});
var Parent = Class.create({
m1: function(){ return 'm1' },
@ -130,7 +126,5 @@ new Test.Unit.Runner({
this.assertEqual("toString", new Foo().toString());
this.assertEqual("valueOf", new Foo().valueOf());
this.assertEqual("toString", new Bar().toString());
this.assertEqual("myValueOf", new Bar().valueOf());
}
});

View File

@ -73,15 +73,7 @@ new Test.Unit.Runner({
this.assertElementsMatch(document.getElementsByClassName('A'), 'p.A', 'ul#class_names_ul.A', 'li.A.C');
var isElementPrototypeSupported = (function(){
var el = document.createElement('div');
var result = typeof el.show != 'undefined';
el = null;
return result;
})();
if (!isElementPrototypeSupported)
if (Prototype.Browser.IE)
this.assertUndefined(document.getElementById('unextended').show);
this.assertElementsMatch(div.getElementsByClassName('B'), 'ul#class_names_ul.A.B', 'div.B.C.D');
@ -103,8 +95,7 @@ new Test.Unit.Runner({
this.assertElementsMatch(list.getElementsByClassName({}));
// those lookups shouldn't have extended all nodes in document
if (!isElementPrototypeSupported)
this.assertUndefined(document.getElementById('unextended')['show']);
if (Prototype.Browser.IE) this.assertUndefined(document.getElementById('unextended')['show']);
},
testElementInsertWithHTML: function() {
@ -378,15 +369,6 @@ new Test.Unit.Runner({
this.assertEqual('hello world', getInnerHTML('testdiv'));
},
testElementUpdateScriptElement: function() {
var el = new Element('script', {
type: 'text/javascript'
});
this.assertNothingRaised(function(){
el.update('(function(){})');
})
},
testElementReplace: function() {
$('testdiv-replace-1').replace('hello from div!');
this.assertEqual('hello from div!', $('testdiv-replace-container-1').innerHTML);
@ -641,20 +623,6 @@ new Test.Unit.Runner({
},
testElementExtend: function() {
Element.Methods.Simulated.simulatedMethod = function() {
return 'simulated';
};
Element.addMethods();
function testTag(tagName) {
var element = document.createElement(tagName);
this.assertEqual(element, Element.extend(element));
// test method from Methods
this.assertRespondsTo('show', element);
// test method from Simulated
this.assertRespondsTo('simulatedMethod', element);
}
var element = $('element_extend_test');
this.assertRespondsTo('show', element);
@ -679,9 +647,6 @@ new Test.Unit.Runner({
this.assertEqual(textnode, Element.extend(textnode));
this.assert(typeof textnode['show'] == 'undefined');
}, this);
// clean up
delete Element.Methods.Simulated.simulatedMethod;
},
testElementExtendReextendsDiscardedNodes: function() {
@ -840,29 +805,7 @@ new Test.Unit.Runner({
this.assert(
$('style_test_3').setOpacity(0.9999999).getStyle('opacity') > 0.999
);
/*
IE <= 7 needs a `hasLayout` for opacity ("filter") to function properly
`hasLayout` is triggered by setting `zoom` style to `1`,
In IE8 setting `zoom` does not affect `hasLayout`
and IE8 does not even need `hasLayout` for opacity to work
We do a proper feature test here
*/
var ZOOM_AFFECT_HAS_LAYOUT = (function(){
// IE7
var el = document.createElement('div');
el.style.zoom = 1;
var result = el.hasLayout;
el = null;
return result;
})();
if (ZOOM_AFFECT_HAS_LAYOUT) {
if (Prototype.Browser.IE) {
this.assert($('style_test_4').setOpacity(0.5).currentStyle.hasLayout);
this.assert(2, $('style_test_5').setOpacity(0.5).getStyle('zoom'));
this.assert(0.5, new Element('div').setOpacity(0.5).getOpacity());
@ -951,9 +894,6 @@ 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() {
@ -967,7 +907,7 @@ new Test.Unit.Runner({
this.assertEqual('a link' , $('attributes_with_issues_1').readAttribute('title'));
$('cloned_element_attributes_issue').readAttribute('foo')
var clone = $('cloned_element_attributes_issue').clone(true);
var clone = $('cloned_element_attributes_issue').cloneNode(true);
clone.writeAttribute('foo', 'cloned');
this.assertEqual('cloned', clone.readAttribute('foo'));
this.assertEqual('original', $('cloned_element_attributes_issue').readAttribute('foo'));
@ -1124,20 +1064,13 @@ new Test.Unit.Runner({
{id: 'my_input_field_id', name: 'my_input_field'}));
this.assertEqual(input, document.body.lastChild);
this.assertEqual('my_input_field', $(document.body.lastChild).name);
if ('outerHTML' in document.documentElement) {
this.assertMatch(/name=["']?my_input_field["']?/, $('my_input_field_id').outerHTML);
}
if (Prototype.Browser.IE)
this.assertMatch(/name=["']?my_input_field["']?/, $('my_input_field').outerHTML);
if (originalElement && Prototype.BrowserFeatures.ElementExtensions) {
Element.prototype.fooBar = Prototype.emptyFunction
this.assertRespondsTo('fooBar', new Element('div'));
}
elWithClassName = new Element('div', { 'className': 'firstClassName' });
this.assert(elWithClassName.hasClassName('firstClassName'));
elWithClassName = new Element('div', { 'class': 'firstClassName' });
this.assert(elWithClassName.hasClassName('firstClassName'));
},
testElementGetHeight: function() {
@ -1325,10 +1258,6 @@ 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() {
@ -1436,69 +1365,6 @@ new Test.Unit.Runner({
constants.each(function(pair) {
this.assertEqual(Node[pair.key], pair.value);
}, this);
},
testElementStorage: function() {
var element = $('test-empty');
element.store('foo', 'bar');
this.assertEqual("bar", element.retrieve("foo"), "Setting and reading a property");
var result = element.store('foo', 'thud');
this.assertEqual("thud", element.retrieve("foo"), "Re-setting and reading property");
this.assertIdentical(element, result, "Element#store should return element");
element.store('bar', 'narf');
this.assertEnumEqual($w('foo bar'), element.getStorage().keys(), "Getting the storage hash");
element.getStorage().unset('bar');
this.assertEnumEqual($w('foo'), element.getStorage().keys(), "Getting the storage hash after unsetting a key");
element.store({ 'narf': 'narf', 'zort': 'zort' });
this.assertEqual("narf", element.retrieve('narf'), "Storing multiple properties at once");
this.assertEqual("zort", element.retrieve('zort'), "Storing multiple properties at once");
this.assertUndefined(element.retrieve('bar'), "Undefined key should return undefined if default value is not defined");
this.assertEqual("default", element.retrieve('bar', 'default'), "Return default value if undefined key");
this.assertEqual("default", element.retrieve('bar'), "Makes sure default value has been set properly");
var clonedElement = $('test-empty').clone(false);
this.assert(Object.isUndefined(clonedElement._prototypeUID), "Cloning a node should not confuse the storage engine");
},
testElementClone: function() {
var element = new Element('div', {
title: 'bar'
});
element.className = 'foo';
// add child
element.update('<span id="child">child node</span>');
// add observer
element.observe('click', Prototype.emptyFunction);
// add observer on a child
element.down('span').observe('dblclick', Prototype.emptyFunction);
var shallowClone = element.clone();
var deepClone = element.clone(true);
var assertCloneTraits = (function(clone) {
this.assert(clone); // exists
this.assert(clone.show); // is extended
this.assertEqual('DIV', clone.nodeName.toUpperCase()); // proper nodeName
this.assertEqual('foo', clone.className); // proper attributes
this.assertEqual('bar', clone.title);
this.assert(!clone._prototypeUID); // _prototypeUID does not exist
}).bind(this);
// test generic traits of both deep and shallow clones first
assertCloneTraits(shallowClone);
assertCloneTraits(deepClone);
// test deep clone traits
this.assert(deepClone.firstChild);
this.assertEqual('SPAN', deepClone.firstChild.nodeName.toUpperCase());
this.assert(!deepClone.down('span')._prototypeUID);
}
});
@ -1514,4 +1380,4 @@ function preservingBrowserDimensions(callback) {
} finally {
window.resizeTo(original.width, original.height);
}
}
}

View File

@ -136,14 +136,6 @@ new Test.Unit.Runner({
this.assertEnumEqual($('grepHeader', 'grepCell'),
$('grepTable', 'grepTBody', 'grepRow', 'grepHeader', 'grepCell').grep(new Selector('.cell')));
// troublesome characters
this.assertEnumEqual(['?a', 'c?'], ['?a','b','c?'].grep('?'));
this.assertEnumEqual(['*a', 'c*'], ['*a','b','c*'].grep('*'));
this.assertEnumEqual(['+a', 'c+'], ['+a','b','c+'].grep('+'));
this.assertEnumEqual(['{1}a', 'c{1}'], ['{1}a','b','c{1}'].grep('{1}'));
this.assertEnumEqual(['(a', 'c('], ['(a','b','c('].grep('('));
this.assertEnumEqual(['|a', 'c|'], ['|a','b','c|'].grep('|'));
},
testInclude: function() {

View File

@ -109,24 +109,6 @@ new Test.Unit.Runner({
span.fire("test:somethingElseHappened");
this.assertEqual(2, count);
},
testMultipleEventHandlersCanBeAddedAndRemovedFromAnElement: function() {
var span = $("span"), count1 = 0, count2 = 0;
var observer1 = function() { count1++ };
var observer2 = function() { count2++ };
span.observe("test:somethingHappened", observer1);
span.observe("test:somethingHappened", observer2);
span.fire("test:somethingHappened");
this.assertEqual(1, count1);
this.assertEqual(1, count2);
span.stopObserving("test:somethingHappened", observer1);
span.stopObserving("test:somethingHappened", observer2);
span.fire("test:somethingHappened");
this.assertEqual(1, count1);
this.assertEqual(1, count2);
},
testStopObservingWithoutArguments: function() {
var span = $("span"), count = 0, observer = function() { count++ };
@ -156,27 +138,23 @@ new Test.Unit.Runner({
},
testStopObservingRemovesHandlerFromCache: function() {
var span = $("span"), observer = Prototype.emptyFunction, eventID;
var span = $("span"), observer = function() { }, eventID;
span.observe("test:somethingHappened", observer);
eventID = span._prototypeEventID;
var registry = span.getStorage().get('prototype_event_registry');
this.assert(registry);
this.assert(Object.isArray(registry.get('test:somethingHappened')));
this.assertEqual(1, registry.get('test:somethingHappened').length);
this.assert(Event.cache[eventID]);
this.assert(Object.isArray(Event.cache[eventID]["test:somethingHappened"]));
this.assertEqual(1, Event.cache[eventID]["test:somethingHappened"].length);
span.stopObserving("test:somethingHappened", observer);
registry = span.getStorage().get('prototype_event_registry');
this.assert(registry);
this.assert(Object.isArray(registry.get('test:somethingHappened')));
this.assertEqual(0, registry.get('test:somethingHappened').length);
this.assert(Event.cache[eventID]);
this.assert(Object.isArray(Event.cache[eventID]["test:somethingHappened"]));
this.assertEqual(0, Event.cache[eventID]["test:somethingHappened"].length);
},
testObserveAndStopObservingAreChainable: function() {
var span = $("span"), observer = Prototype.emptyFunction;
var span = $("span"), observer = function() { };
this.assertEqual(span, span.observe("test:somethingHappened", observer));
this.assertEqual(span, span.stopObserving("test:somethingHappened", observer));
@ -184,8 +162,6 @@ new Test.Unit.Runner({
span.observe("test:somethingHappened", observer);
this.assertEqual(span, span.stopObserving("test:somethingHappened"));
this.assertEqual(span, span.stopObserving("test:somethingOtherHappened", observer));
span.observe("test:somethingHappened", observer);
this.assertEqual(span, span.stopObserving());
this.assertEqual(span, span.stopObserving()); // assert it again, after there are no observers
@ -212,7 +188,7 @@ new Test.Unit.Runner({
testEventStopped: function() {
var span = $("span"), event;
span.observe("test:somethingHappened", Prototype.emptyFunction);
span.observe("test:somethingHappened", function() { });
event = span.fire("test:somethingHappened");
this.assert(!event.stopped, "event.stopped should be false with an empty observer");
span.stopObserving("test:somethingHappened");

View File

@ -0,0 +1,6 @@
<div id="test"></div>
<ul id="list">
<li></li>
<li></li>
<li></li>
</ul>

106
test/unit/fixtures/base.js Normal file
View File

@ -0,0 +1,106 @@
var Person = function(name){
this.name = name;
};
Person.prototype.toJSON = function() {
return '-' + this.name;
};
var arg1 = 1;
var arg2 = 2;
var arg3 = 3;
function TestObj() { };
TestObj.prototype.assertingEventHandler =
function(event, assertEvent, assert1, assert2, assert3, a1, a2, a3) {
assertEvent(event);
assert1(a1);
assert2(a2);
assert3(a3);
};
var globalBindTest = null;
// base class
var Animal = Class.create({
initialize: function(name) {
this.name = name;
},
name: "",
eat: function() {
return this.say("Yum!");
},
say: function(message) {
return this.name + ": " + message;
}
});
// subclass that augments a method
var Cat = Class.create(Animal, {
eat: function($super, food) {
if (food instanceof Mouse) return $super();
else return this.say("Yuk! I only eat mice.");
}
});
// empty subclass
var Mouse = Class.create(Animal, {});
//mixins
var Sellable = {
getValue: function(pricePerKilo) {
return this.weight * pricePerKilo;
},
inspect: function() {
return '#<Sellable: #{weight}kg>'.interpolate(this);
}
};
var Reproduceable = {
reproduce: function(partner) {
if (partner.constructor != this.constructor || partner.sex == this.sex)
return null;
var weight = this.weight / 10, sex = Math.random(1).round() ? 'male' : 'female';
return new this.constructor('baby', weight, sex);
}
};
// base class with mixin
var Plant = Class.create(Sellable, {
initialize: function(name, weight) {
this.name = name;
this.weight = weight;
},
inspect: function() {
return '#<Plant: #{name}>'.interpolate(this);
}
});
// subclass with mixin
var Dog = Class.create(Animal, Reproduceable, {
initialize: function($super, name, weight, sex) {
this.weight = weight;
this.sex = sex;
$super(name);
}
});
// subclass with mixins
var Ox = Class.create(Animal, Sellable, Reproduceable, {
initialize: function($super, name, weight, sex) {
this.weight = weight;
this.sex = sex;
$super(name);
},
eat: function(food) {
if (food instanceof Plant)
this.weight += food.weight;
},
inspect: function() {
return '#<Ox: #{name}>'.interpolate(this);
}
});

View File

@ -53,17 +53,6 @@
</span>
</span>
</div> <!-- #dupContainer -->
<div id="dupContainer.withdot:active">
<span id="dupL1_dotcolon" class="span_foo span_bar">
<span id="dupL2_dotcolon">
<span id="dupL3_dotcolon">
<span id="dupL4_dotcolon">
<span id="dupL5_dotcolon"></span>
</span>
</span>
</span>
</span>
</div> <!-- #dupContainer.withdot:active -->
<div id="grandfather"> grandfather
<div id="father" class="brothers men"> father
@ -77,12 +66,4 @@
<input type="hidden" id="commaTwo" name="foo2" value="oops" />
</form>
<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

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

View File

@ -26,10 +26,6 @@ new Test.Unit.Runner({
this.assertEqual("4", $F("input_enabled"));
},
testFormReset: function() {
this.assert(!Object.isUndefined(Form.reset('form').reset));
},
testFormElementEventObserver: function(){
var callbackCounter = 0;
var observer = new Form.Element.EventObserver('input_enabled', function(){
@ -291,8 +287,8 @@ new Test.Unit.Runner({
form = $('bigform');
var input = form['tf_text'], select = form['tf_selectOne'];
input._extendedByPrototype = select._extendedByPrototype = void 0;
input._extendedByPrototype = select._extendedByPrototype = false;
this.assert($(input).anInputMethod);
this.assert(!input.aSelectMethod);
this.assertEqual('input', input.anInputMethod());
@ -317,7 +313,7 @@ new Test.Unit.Runner({
// with empty action attribute
request = $("ffe").request({ method: 'post' });
this.assert(request.url.include("/tmp/form_test.html"),
this.assert(request.url.include("unit/tmp/form_test.html"),
'wrong default action for form element with empty action attribute');
},
@ -359,24 +355,5 @@ new Test.Unit.Runner({
'multiple select options improperly set');
input.setValue(['1', '3']);
this.assertEnumEqual(['1', '3'], input.getValue());
},
testSerializeFormTroublesomeNames: function() {
var el = new Element('form', {
action: '/'
});
var input = new Element('input', {
type: 'text',
name: 'length',
value: 'foo'
});
var input2 = new Element('input', {
type: 'text',
name: 'bar',
value: 'baz'
});
el.appendChild(input);
el.appendChild(input2);
this.assertHashEqual({ length: 'foo', bar: 'baz' }, el.serialize(true));
}
});

View File

@ -129,28 +129,22 @@ new Test.Unit.Runner({
testObjectIsString: function() {
this.assert(!Object.isString(function() { }));
this.assert(Object.isString("a string"));
this.assert(Object.isString(new String("a string")));
this.assert(!Object.isString(0));
this.assert(!Object.isString([]));
this.assert(!Object.isString({}));
this.assert(!Object.isString(false));
this.assert(!Object.isString(undefined));
this.assert(!Object.isString(document), 'host objects should return false rather than throw exceptions');
},
testObjectIsNumber: function() {
this.assert(Object.isNumber(0));
this.assert(Object.isNumber(1.0));
this.assert(Object.isNumber(new Number(0)));
this.assert(Object.isNumber(new Number(1.0)));
this.assert(!Object.isNumber(function() { }));
this.assert(!Object.isNumber({ test: function() { return 3 } }));
this.assert(!Object.isNumber("a string"));
this.assert(!Object.isNumber([]));
this.assert(!Object.isNumber({}));
this.assert(!Object.isNumber(false));
this.assert(!Object.isNumber(undefined));
this.assert(!Object.isNumber(document), 'host objects should return false rather than throw exceptions');
},
testObjectIsUndefined: function() {

View File

@ -11,25 +11,5 @@ new Test.Unit.Runner({
this.wait(600, function() {
this.assertEqual(3, peEventCount);
});
},
testOnTimerEventMethod: function() {
var testcase = this,
pe = {
onTimerEvent: PeriodicalExecuter.prototype.onTimerEvent,
execute: function() {
testcase.assert(pe.currentlyExecuting);
}
};
pe.onTimerEvent();
this.assert(!pe.currentlyExecuting);
pe.execute = function() {
testcase.assert(pe.currentlyExecuting);
throw new Error()
}
this.assertRaise('Error', pe.onTimerEvent.bind(pe));
this.assert(!pe.currentlyExecuting);
}
});

View File

@ -1,50 +0,0 @@
/*
<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

@ -1,9 +1,5 @@
var $RunBenchmarks = false;
function reduce(arr) {
return arr.length > 1 ? arr : arr[0];
}
new Test.Unit.Runner({
testSelectorWithTagName: function() {
@ -91,10 +87,6 @@ new Test.Unit.Runner({
this.assertEnumEqual($$('#troubleForm *[type]'), $$('#troubleForm [type]'));
},
testSelectorWithAttributeContainingDash: function() {
this.assertEnumEqual([$('attr_with_dash')], $$('[foo-bar]'), "attribute with hyphen");
},
testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {
this.assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]'));
this.assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]'));
@ -197,12 +189,12 @@ new Test.Unit.Runner({
testSelectorWithAdjacence: function() {
this.assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers'));
this.assertEnumEqual([$('uncle')], $$('div.brothers + div'));
this.assertEqual($('level2_2'), reduce($$('#level2_1+span')));
this.assertEqual($('level2_2'), reduce($$('#level2_1 + span')));
this.assertEqual($('level2_2'), reduce($$('#level2_1 + *')));
this.assertEqual($('level2_2'), $$('#level2_1+span').reduce());
this.assertEqual($('level2_2'), $$('#level2_1 + span').reduce());
this.assertEqual($('level2_2'), $$('#level2_1 + *').reduce());
this.assertEnumEqual([], $$('#level2_2 + span'));
this.assertEqual($('level3_2'), reduce($$('#level3_1 + span')));
this.assertEqual($('level3_2'), reduce($$('#level3_1 + *')));
this.assertEqual($('level3_2'), $$('#level3_1 + span').reduce());
this.assertEqual($('level3_2'), $$('#level3_1 + *').reduce());
this.assertEnumEqual([], $$('#level3_2 + *'));
this.assertEnumEqual([], $$('#level3_1 + em'));
$RunBenchmarks && this.wait(500, function() {
@ -212,8 +204,8 @@ new Test.Unit.Runner({
testSelectorWithLaterSibling: function() {
this.assertEnumEqual([$('list')], $$('h1 ~ ul'));
this.assertEqual($('level2_2'), reduce($$('#level2_1 ~ span')));
this.assertEnumEqual($('level2_2', 'level2_3'), reduce($$('#level2_1 ~ *')));
this.assertEqual($('level2_2'), $$('#level2_1 ~ span').reduce());
this.assertEnumEqual($('level2_2', 'level2_3'), $$('#level2_1 ~ *').reduce());
this.assertEnumEqual([], $$('#level2_2 ~ span'));
this.assertEnumEqual([], $$('#level3_2 ~ *'));
this.assertEnumEqual([], $$('#level3_1 ~ em'));
@ -356,6 +348,14 @@ 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'),
@ -367,48 +367,11 @@ new Test.Unit.Runner({
'div.is_counted'
);
},
testSelectorNotInsertedNodes: function() {
window.debug = true;
var wrapper = new Element("div");
wrapper.update("<table><tr><td id='myTD'></td></tr></table>");
this.assertNotNullOrUndefined(wrapper.select('[id=myTD]')[0],
'selecting: [id=myTD]');
this.assertNotNullOrUndefined(wrapper.select('#myTD')[0],
'selecting: #myTD');
this.assertNotNullOrUndefined(wrapper.select('td')[0],
'selecting: td');
this.assert($$('#myTD').length == 0,
'should not turn up in document-rooted search');
window.debug = false;
},
testElementDown: function() {
var a = $('dupL4');
var b = $('dupContainer').down('#dupL4');
this.assertEqual(a, b);
},
testElementDownWithDotAndColon: function() {
var a = $('dupL4_dotcolon');
var b = $('dupContainer.withdot:active').down('#dupL4_dotcolon');
var c = $('dupContainer.withdot:active').select('#dupL4_dotcolon');
this.assertEqual(a, b);
this.assertEnumEqual([a], c);
},
testDescendantSelectorBuggy: function() {
var el = document.createElement('div');
el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
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

@ -44,11 +44,6 @@ new Test.Unit.Runner({
'ウィメンズ2007\nクルーズコレクション'.gsub(/\n/,'<br/>'));
this.assertEqual('ウィメンズ2007<br/>クルーズコレクション',
'ウィメンズ2007\nクルーズコレクション'.gsub('\n','<br/>'));
this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar',
source.gsub('', 'bar'));
this.assertEqual('barfbarobarobar barbbarobarobar barbbarobarzbar',
source.gsub(new RegExp(''), 'bar'));
},
testGsubWithReplacementTemplateString: function() {
@ -64,19 +59,6 @@ new Test.Unit.Runner({
source.gsub(/(.)(o+)/, '#{3}'));
},
testGsubWithTroublesomeCharacters: function() {
this.assertEqual('ab', 'a|b'.gsub('|', ''));
//'ab'.gsub('', ''); // freeze
this.assertEqual('ab', 'ab(?:)'.gsub('(?:)', ''));
this.assertEqual('ab', 'ab()'.gsub('()', ''));
this.assertEqual('ab', 'ab'.gsub('^', ''));
this.assertEqual('ab', 'a?b'.gsub('?', ''))
this.assertEqual('ab', 'a+b'.gsub('+', ''));
this.assertEqual('ab', 'a*b'.gsub('*', ''));
this.assertEqual('ab', 'a{1}b'.gsub('{1}', ''));
this.assertEqual('ab', 'a.b'.gsub('.', ''));
},
testSubWithReplacementFunction: function() {
var source = 'foo boo boz';
@ -208,7 +190,6 @@ new Test.Unit.Runner({
this.assertEqual('hello world', '<a href="#" onclick="moo!">hello</a> world'.stripTags());
this.assertEqual('hello world', 'h<b><em>e</em></b>l<i>l</i>o w<span class="moo" id="x"><b>o</b></span>rld'.stripTags());
this.assertEqual('1\n2', '1\n2'.stripTags());
this.assertEqual('one < two blah baz', 'one < two <a href="#" title="foo > bar">blah</a> <input disabled>baz'.stripTags());
},
testStripScripts: function() {
@ -269,8 +250,6 @@ new Test.Unit.Runner({
this.assertEqual('1\n2', '1\n2'.unescapeHTML());
this.assertEqual('Pride & Prejudice', '<h1>Pride &amp; Prejudice</h1>'.unescapeHTML());
this.assertIdentical('&lt;', '&amp;lt;'.unescapeHTML());
this.benchmark(function() { largeTextEscaped.unescapeHTML() }, 1000);
},
@ -317,20 +296,20 @@ new Test.Unit.Runner({
},
testTemplateEvaluationWithIndexing: function() {
var source = '#{0} = #{[0]} - #{1} = #{[1]} - #{[2][0]} - #{[2].name} - #{first[0]} - #{[first][0]} - #{[\]]} - #{first[\]]}';
var source = '#{0} = #{[0]} - #{1} = #{[1]} - #{[2][0]} - #{[2].name} - #{first[0]} - #{[first][0]} - #{[\\]]} - #{first[\\]]}';
var subject = [ 'zero', 'one', [ 'two-zero' ] ];
subject[2].name = 'two-zero-name';
subject.first = subject[2];
subject[']'] = '\\';
subject.first[']'] = 'first\\';
this.assertEqual('zero', new Template('#{[0]}').evaluate(subject), "#{[0]}");
this.assertEqual('one', new Template('#{[1]}').evaluate(subject), "#{[1]}");
this.assertEqual('two-zero', new Template('#{[2][0]}').evaluate(subject), '#{[2][0]}');
this.assertEqual('two-zero-name', new Template('#{[2].name}').evaluate(subject), '#{[2].name}');
this.assertEqual('two-zero', new Template('#{first[0]}').evaluate(subject), '#{first[0]}');
this.assertEqual('\\', new Template('#{[\]]}').evaluate(subject), '#{[\]]}');
this.assertEqual('first\\', new Template('#{first[\]]}').evaluate(subject), '#{first[\]]}');
this.assertEqual('empty - empty2', new Template('#{[]} - #{m[]}').evaluate({ '': 'empty', m: {'': 'empty2'}}), '#{[]} - #{m[]}');
this.assertEqual('zero', new Template('#{[0]}').evaluate(subject));
this.assertEqual('one', new Template('#{[1]}').evaluate(subject));
this.assertEqual('two-zero', new Template('#{[2][0]}').evaluate(subject));
this.assertEqual('two-zero-name', new Template('#{[2].name}').evaluate(subject));
this.assertEqual('two-zero', new Template('#{first[0]}').evaluate(subject));
this.assertEqual('\\', new Template('#{[\\]]}').evaluate(subject));
this.assertEqual('first\\', new Template('#{first[\\]]}').evaluate(subject));
this.assertEqual('empty - empty2', new Template('#{[]} - #{m[]}').evaluate({ '': 'empty', m: {'': 'empty2'}}));
this.assertEqual('zero = zero - one = one - two-zero - two-zero-name - two-zero - two-zero - \\ - first\\', new Template(source).evaluate(subject));
},
@ -342,12 +321,6 @@ new Test.Unit.Runner({
toTemplateReplacements: function() { return { name: this.name, job: this.getJob() } }
};
this.assertEqual('My name is Stephan, my job is Web developer', new Template(source).evaluate(subject));
var strActual = new Template('foo #{bar} baz').evaluate({
toTemplateReplacements: function(){ return null; }
});
this.assertIdentical('foo baz', strActual);
this.assertIdentical('foo', new Template('foo#{bar}').evaluate(null));
},
testTemplateEvaluationCombined: function() {

View File

@ -1,27 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Unit test file | <%= title %> | <%= template_name %> template | <%= timestamp %></title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript" charset="utf-8">
var eventResults = {};
var originalElement = window.Element;
</script>
<%= script_tag('assets/prototype.js') %>
<%= script_tag('lib_assets/unittest.js') %>
<%= link_tag('lib_assets/unittest.css') %>
<%= css_fixtures %>
<%= js_fixtures %>
<%= test_file %>
</head>
<body>
<div id="testlog"></div>
<%= html_fixtures %>
</body>
</html>
<script type="text/javascript" charset="utf-8">
eventResults.endOfDocument = true;
</script>

1
vendor/caja_builder vendored

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

View File

@ -1,803 +0,0 @@
/* 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

@ -1,18 +0,0 @@
//= 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 +0,0 @@
Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664

View File

@ -1,18 +0,0 @@
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;

1
vendor/pdoc vendored

@ -1 +0,0 @@
Subproject commit 472a55dd0019acf034d4f72522915a5e9efd0a1a

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

View File

@ -1,34 +0,0 @@
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;

1
vendor/sprockets vendored

@ -1 +0,0 @@
Subproject commit 8860b7f54d08dd4861bea1ca5de223555ad4b2c1

1
vendor/unittest_js vendored

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