Compare commits

...

391 Commits

Author SHA1 Message Date
John Bintz a36b6d3e76 add unit test for getStyle where passed object does not have style attribute 2010-01-15 14:00:07 -05:00
John Bintz 2ede0b24e2 coding style change 2010-01-12 14:51:36 -05:00
John Bintz 1c21c2f1b6 remaining IE6 fixes for offset positioning where null objects are being returned 2010-01-12 14:46:52 -05:00
John Bintz 8a5b3209f4 add check to getStyle to ensure provided element has a style attribute 2010-01-06 19:43:10 -05:00
John Bintz 5992866e46 Revert "potential workaround for IE 5 issue where element can become something other than a DOM node"
This reverts commit ef78a973ef.
2010-01-06 19:41:33 -05:00
John Bintz ef78a973ef potential workaround for IE 6 issue where element can become something other than a DOM node 2010-01-06 16:52:58 -05:00
Tobie Langel 6839886699 doc: Update PDoc. Default to BlueCloth Markdown parser to avoid Maruku warning. 2009-12-31 19:34:35 +01:00
Juriy Zaytsev 475f1797e1 No need to check for `window.Node`, since `var Node` already declares it if missing. 2009-12-30 01:45:59 -05:00
Juriy Zaytsev e0a102b510 Merge branch 'master' of github.com:sstephenson/prototype 2009-12-30 01:41:27 -05:00
Juriy Zaytsev a260913a30 Remove SETATTRIBUTE_IGNORES_NAME feature test, replacing it with a simpler HAS_EXTENDED_CREATE_ELEMENT_SYNTAX one. This avoids invalid injection of FORM into a root element (HTML). 2009-12-30 01:33:37 -05:00
Tobie Langel 6c1790ac4e doc: Fix Template doc to avoid issues with Sprockets. 2009-12-29 01:54:56 +01:00
Tobie Langel 4cc67f2e8e doc: Minor cleanup. 2009-12-29 01:53:16 +01:00
Tobie Langel 8a8af0a729 doc: Fix documentation for Template. 2009-12-24 07:32:32 +01:00
Tobie Langel 2e3e02d92c doc: Fix documentation errors in Ajax.Response. 2009-12-24 03:19:14 +01:00
Tobie Langel bccb541797 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-12-24 03:18:11 +01:00
Juriy Zaytsev 56fb5b84a1 No need to use expensive try/catch when check for element being orphaned suffices. 2009-12-23 01:54:19 -05:00
Tobie Langel d085d97a56 Revert back to PDoc's default templates. 2009-12-23 06:51:56 +01:00
Tobie Langel fb93b80b59 doc: using pygments for syntax highlighting. 2009-12-23 06:32:59 +01:00
Tobie Langel a7f05ee8b5 doc: Fix a typo in Prototype.Selector.find which prevented proper documentation parsing. Add a default value for the index argument. 2009-12-23 05:52:51 +01:00
Juriy Zaytsev 647d93bcc4 Use native `Array.isArray` when available; currently present in nightly webkit (up to 17x faster) and Opera 10.5 alpha (up to 5x faster). 2009-12-22 18:05:30 -05:00
Andrew Dupont d6ed7efe94 Tweak the PDoc templates. 2009-12-17 14:52:27 -06:00
Andrew Dupont 83b0c153d3 Add examples for String#gsub and String#sub.
(cherry picked from commit 591b25eb2604953754f08c40e9ef99791a6bb51b)
2009-12-04 18:29:40 -06:00
Andrew Dupont c028935279 Add note about mouseenter/leave support to docs.
(cherry picked from commit 1c4c2005341ef587dca7eacc8d347367e3eb7ef7)
2009-12-04 18:28:31 -06:00
Andrew Dupont 0dd600974c Add documentation for event key codes.
(cherry picked from commit 93bd2048e2c106ecc309292be358ef398df05984)
2009-12-04 18:27:49 -06:00
Andrew Dupont e7c9072872 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-12-04 18:25:23 -06:00
Sam Stephenson fa0dce2488 Revert Event#findElement changes from 6bb309a 2009-12-03 00:31:35 -06:00
Sam Stephenson 6bb309afb7 Selector API change: replace Prototype.Selector.filter with Prototype.Selector.find, which works like the old Selector.findElement. 2009-12-02 15:57:58 -06:00
Sam Stephenson 7770ab99dc Fix 'rake' without SELECTOR_ENGINE environment variable set 2009-12-02 15:14:18 -06:00
Tobie Langel a44a8db6ae Avoid automatically fetching the vendor/sizzle git submodule. 2009-12-02 21:41:52 +01:00
Sam Stephenson 797c231bfa Move ext/sizzle into src so it's available in the default Sprockets load path. 2009-12-02 14:18:01 -06:00
Tobie Langel 9d4711281d Fix typo in Rakefile. 2009-12-02 00:47:16 +01:00
Samuel Lebeau 74c5d45511 Fix custom selector engine load path resolution. 2009-11-30 12:45:13 +01:00
Tobie Langel 6b00f24963 Merge branch 'selector_agnostic'
Conflicts:
	src/dom/selector.js
2009-11-30 11:32:09 +01:00
Tobie Langel cdb41a170f Add sizzle to ext/. 2009-11-30 11:12:26 +01:00
Juriy Zaytsev b3fc07922c Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. 2009-11-13 15:49:22 -05:00
Juriy Zaytsev 18f2ac65c3 Optimize Element#immediateDescendants. 2009-11-13 14:28:27 -05:00
Juriy Zaytsev 82c9b9c783 Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. 2009-11-13 14:21:14 -05:00
Juriy Zaytsev 9ef3f8d2ed Eliminate runtime forking and long method lookup in `Element.hasAttribute`. 2009-11-13 12:27:10 -05:00
Juriy Zaytsev 829800834d Do not create translations object every time method is called. 2009-11-12 21:21:42 -05:00
Juriy Zaytsev c272e40042 Minor optimization in class module. 2009-11-12 21:17:37 -05:00
Juriy Zaytsev f40fd5a7d6 Remove redundant ternary. 2009-11-12 21:15:52 -05:00
Juriy Zaytsev 1f167f8754 `if` should be followed by space. 2009-11-11 17:43:25 -05:00
Juriy Zaytsev 01a229011a Avoid repeating declaration statements. 2009-11-11 17:32:19 -05:00
Andrew Dupont 5d027eb939 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-10-30 02:59:14 -05:00
Tobie Langel 678774cbd6 Minor changes to the Sizzle adapter. 2009-10-26 02:00:55 +01:00
Tobie Langel 74ae0a5537 Renamed Prototype.Legacy to Prototype.LegacySelector by popular request. 2009-10-25 16:35:59 +01:00
Tobie Langel 70c5e98d44 Make the UpdaterHelper function on WebKit-based browsers. 2009-10-25 16:24:51 +01:00
Tobie Langel 24569d1d98 Deprecate the Selector API. 2009-10-25 16:13:11 +01:00
Tobie Langel fdf3424f78 Add unit tests. 2009-10-25 00:40:48 +02:00
Tobie Langel 107f812525 Modify PrototypeHelper.sprocketize to take a hash of options rather than separate arguments. 2009-10-24 22:17:37 +02:00
Tobie Langel 83826829a7 Add Prototype.Selector.engine which simply holds a reference to the actual selector engine used. 2009-10-24 16:12:38 +02:00
Tobie Langel 5f85799c3f Refactor NWMatcher adapter. 2009-10-23 19:29:03 +02:00
Tobie Langel 75aab03eba Repo and Rakefile refactoring. 2009-10-23 18:32:07 +02:00
Tobie Langel 7f5ce1e6c2 Clean-up NWMatcher proxy. 2009-10-23 13:17:53 +02:00
Tobie Langel f6f6955a71 Marked old Selector API as deprecated. 2009-10-23 12:58:10 +02:00
Tobie Langel b0159bdba7 Document Prototype.Selector API. 2009-10-23 12:53:03 +02:00
Tobie Langel af89847a4f Make Event.stopObserving return element in all cases. [#810 state:resolved] 2009-10-23 07:51:50 +02:00
Tobie Langel bf8e404805 Make Event.stopObserving return element in all cases. [#810 state:resolved] 2009-10-23 07:49:57 +02:00
Tobie Langel ed27b225a5 Fix Element extension when using the NWMatcher selector engine. (jddalton) 2009-10-23 07:31:37 +02:00
Tobie Langel 15c323b9ac Include NWMatcher as a submodule. 2009-10-23 07:18:52 +02:00
Tobie Langel 17e8064d8a Add legacy Prototype selector engine. Acessible as Prototype.Legacy. Use SELECTOR_ENGINE=legacy to build. 2009-10-23 06:39:59 +02:00
Tobie Langel cba5468b09 Nitpicking. 2009-10-23 06:38:27 +02:00
Tobie Langel 7762e002cb Reorder repository to allow for custom selector engines to be included instead of Sizzle (the current default). Selector engines belong in the vendor directory and must have a selector_engine.js file. To build a Prototype with your engine of choice just spepcify it at build time using the SELECTOR_ENGINE option. For example, to build a version with NSMatcher: rake dist SELECTOR_ENGINE=nwmatcher. 2009-10-23 05:49:40 +02:00
Tobie Langel 3e19f959a2 Fix Selector#findElements. 2009-10-23 04:42:05 +02:00
Tobie Langel da3e1e361e Clean-up. 2009-10-23 04:10:30 +02:00
Tobie Langel 2d13d45dc8 Remove dependencies to Selector in favor of Prototype.Selector throughout the library. 2009-10-23 01:43:48 +02:00
Tobie Langel caf66395d5 Add Prototype.Selector object with select, match and filter methods as a wraper around Sizzle.Redefine the whole of Selector API in terms of Prototype.Selector. 2009-10-23 01:17:53 +02:00
Yaffle 2d3e423230 Add missing semicolons. [#837 state:resolved] 2009-10-21 18:25:27 +02:00
Tobie Langel f9c680a9ba More nitpicking. 2009-10-21 17:58:09 +02:00
Tobie Langel 8783065b8e Cosmetic rewrite of String#startsWith and String#endsWith with performance-related comments. 2009-10-21 17:24:09 +02:00
Tobie Langel 35ed99ba2e doc: nitpicking. 2009-10-21 17:00:17 +02:00
Tobie Langel 067a0ec364 Update to latest PDoc release. 2009-10-21 16:35:43 +02:00
Andrew Dupont d3df9ba400 Automate installation of Git submodules. 2009-10-11 18:49:14 -05:00
Andrew Dupont c5372d81f3 Detect if Sizzle hasn't been loaded via submodule. 2009-10-10 19:07:37 -05:00
Sam Stephenson 4d042a9fa0 Match each element individually in Selector.findElement since Sizzle.matches does not preserve order 2009-10-10 13:24:04 -05:00
tjcrowder 859197ca8b doc: Merged/updated old docs for Element.descendants. 2009-10-09 17:20:39 +01:00
tjcrowder d10aad7bfa doc: Merged/updated old docs for Element.descendantOf. 2009-10-09 17:15:15 +01:00
tjcrowder adf80ad1b3 doc: Merged/updated old docs for Element.cumulativeScrollOffset, clarified units, added example. 2009-10-09 17:10:13 +01:00
tjcrowder f1f6fca60b doc: Clarified units in Element.cumulativeOffset and added example. 2009-10-09 17:01:40 +01:00
tjcrowder c448b38f7b doc: Merged/updated old docs for Element.clonePosition. 2009-10-09 16:55:42 +01:00
tjcrowder 9ce1ea06b5 doc: Merged/updated old docs for Element.cleanWhitespace. 2009-10-09 16:10:44 +01:00
tjcrowder 1e29e3c6c9 doc: Merged/updated old docs for Element.classNames, mostly by marking it deprecated. 2009-10-09 15:49:54 +01:00
tjcrowder 892eb9d6b3 doc: Merged/updated old docs for Element.childElements / Element.immediateDescendants. Made immediateDescendants an alias of childElements rather than vice-versa as the latter is depreceated. 2009-10-09 15:34:39 +01:00
tjcrowder e3c89c08c6 doc: Merged/updated old docs for Element.ancestors. 2009-10-09 14:48:47 +01:00
tjcrowder 402a2d408e doc: Merged/updated old docs for Element.adjacent 2009-10-09 14:31:02 +01:00
tjcrowder 77832408bd doc: Merged/updated old docs for Element.addClassName 2009-10-09 14:14:14 +01:00
tjcrowder 7df62ce864 doc: Merged/updated old docs for Element.extend 2009-10-09 13:56:39 +01:00
tjcrowder d6d3ab1fef doc: Merged/updated old docs for Element constructor 2009-10-09 13:36:14 +01:00
tjcrowder 7d073ad56a doc: Merged/updated old docs for Element overview 2009-10-09 13:35:38 +01:00
tjcrowder d2874c0294 doc: Merged old docs for Element.addMethods. 2009-10-08 14:46:14 +01:00
Juriy Zaytsev 3b525f194d String#startsWith, String#endsWith performance optimization [#808 state:resolved] 2009-09-28 19:21:37 -04:00
Samuel Lebeau f4ea4c6ef7 Rewrite `String#camelize` using `String#replace` with a replacement function [#297 state:resolved] 2009-09-26 13:36:42 -04:00
Sam Stephenson 79cf30aab1 Extend matched elements 2009-09-19 16:17:43 -05:00
Sam Stephenson 4dd878f237 Replace Prototype's Selector implementation with Sizzle 2009-09-19 14:42:59 -05:00
Sam Stephenson 9e4a7ce8e8 Don't call private Selector methods in Element#previous and Element#next 2009-09-19 14:40:01 -05:00
Andrew Dupont 4dedcd3b62 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-09-15 18:27:56 -05:00
Andrew Dupont 58a2f9db28 Update to latest PDoc. 2009-09-15 18:27:46 -05:00
Andrew Dupont 19615e7a00 Cleanup on PDoc templates. 2009-09-15 18:27:35 -05:00
tjcrowder cfc7e7a2e7 doc: Convert all subheadings to H5s throughout. 2009-09-10 17:45:11 +01:00
tjcrowder f4314a8789 doc: Merged and updated old docs for Object.clone. 2009-09-10 17:12:38 +01:00
tjcrowder 63ea557b06 doc: Merged and updated old docs for Number#toPaddedString. 2009-09-10 16:51:36 +01:00
tjcrowder a47d9e1c2f doc: Merged and updated old docs for Number#toColorPart. 2009-09-10 16:32:36 +01:00
tjcrowder e6b6193124 doc: Fleshed out Number#times. 2009-09-10 16:30:19 +01:00
tjcrowder 20724ea1f9 doc: Fleshed out Number#round slightly. 2009-09-10 16:18:59 +01:00
tjcrowder b12ec913d7 doc: Fleshed out Number#floor slightly. 2009-09-10 16:18:33 +01:00
tjcrowder e05a089b33 doc: Fleshed out Number#ceil slightly. 2009-09-10 16:18:12 +01:00
tjcrowder e5fa0928e8 doc: Fleshed out Number#abs slightly. 2009-09-10 16:17:35 +01:00
tjcrowder 917f10b574 doc: Modified Number preamble to leave out unnecessary reference to Ruby. 2009-09-10 16:15:21 +01:00
tjcrowder fe5290e15e doc: Fixed missing quote in example for Hash#keys and missing arrows in example for Hash#each. 2009-09-10 15:45:05 +01:00
tjcrowder 3ee5b5ddd8 doc: Merged and updated old docs for Hash#values. 2009-09-10 15:43:29 +01:00
tjcrowder 1910e08a79 doc: Merged and updated old docs for Hash#update. 2009-09-10 15:42:04 +01:00
tjcrowder 71e07f0efa doc: Merged and updated old docs for Hash#unset. 2009-09-10 15:39:34 +01:00
tjcrowder da402268ac doc: Merged and updated old docs for Hash#toQueryString. 2009-09-10 15:37:26 +01:00
tjcrowder 48a0f5a44c doc: Merged and updated old docs for Hash#toObject. 2009-09-10 15:18:05 +01:00
tjcrowder 64cd05dd59 doc: Fleshed out docs for Hash#toJSON. 2009-09-10 15:15:36 +01:00
tjcrowder 62a3c8fb38 doc: Merged and updated old docs for Hash#get 2009-09-10 15:12:56 +01:00
tjcrowder ab5a19a1c1 doc: Merged old docs for Hash#merge, updated example. 2009-09-10 15:07:28 +01:00
tjcrowder 305e79e5d3 doc: Merged old docs for Hash#keys, updated example. 2009-09-10 15:00:34 +01:00
tjcrowder e02a1cf5c6 doc: Merged old docs for Hash#get. 2009-09-10 14:57:10 +01:00
tjcrowder 00ebde59a2 doc: Merged old docs for Hash#each. 2009-09-10 14:55:12 +01:00
tjcrowder f457097afa doc: Minor grammar update to Hash#clone. 2009-09-10 14:13:28 +01:00
tjcrowder 1bab56cfb7 doc: Corrected and new Hash docs, they were describe the 1.5 behavior of not cloning. 2009-09-10 14:04:27 +01:00
tjcrowder 4905d1777c doc: Fixed name of argument in Function#wrap docs. 2009-09-10 14:03:07 +01:00
tjcrowder cdaaaa6421 doc: Merged old docs for Function#wrap, expanded on the signature of the wrapper. 2009-09-10 13:43:01 +01:00
tjcrowder d441752778 doc: Merged and updated old docs for Function#methodize. 2009-09-10 13:29:27 +01:00
tjcrowder 037a47d68a Added missing semicolon in Function#delay. 2009-09-10 13:15:46 +01:00
tjcrowder 9300bd0350 doc: Fleshed out docs on Function#delay. 2009-09-10 13:15:30 +01:00
tjcrowder d4aa3b7b4a doc: Fleshed out docs on Function#defer. 2009-09-10 13:11:30 +01:00
tjcrowder 5f02032763 doc: Fleshed out docs on Function#curry. 2009-09-10 13:06:31 +01:00
tjcrowder c7d0bcdb6c doc: Fleshed out docs on Function#bindAsEventListener and added new example. 2009-09-10 12:53:30 +01:00
tjcrowder 853e0fbbec doc: Update Function#bind documentation, adding examples (simpler, less rambling ones than the old docs). 2009-09-10 11:33:11 +01:00
tjcrowder 29b81fda69 doc: Merged old docs for Function#argumentNames. 2009-09-10 10:31:56 +01:00
Andrew Dupont 5ccf8cbefd Change all H4s in _method_ documentation blocks to H5s. (Start with H5 when using headings in a method doc block; start with H4 when using headings in a namespace/class doc block.) 2009-09-09 23:18:45 -05:00
Tobie Langel cf72372092 doc: Clean up the documentation of Enumerable. 2009-09-08 03:00:23 +02:00
tjcrowder 47c7e12cc6 doc: merged and updated old docs for Enumerable#zip 2009-09-08 01:22:12 +02:00
tjcrowder 7691177374 doc: merged old docs for Enumerable#toArray 2009-09-08 01:21:11 +02:00
tjcrowder 6a702b420c doc: merged and corrected old docs for Enumerable#sortBy 2009-09-08 01:20:35 +02:00
tjcrowder 25ae6b44ce doc: merged and updated old docs for Enumerable#pluck 2009-09-08 01:19:21 +02:00
tjcrowder 13dfbaeb38 doc: merged and updated old docs for Enumerable#partition 2009-09-08 01:03:08 +02:00
tjcrowder cdfd3d5abe doc: merged and updated old docs for Enumerable#min and Enumerable#max 2009-09-08 01:01:58 +02:00
tjcrowder 1a86651055 doc: merged old docs for Enumerable#invoke. 2009-09-08 00:56:14 +02:00
tjcrowder 650d49286d doc: merged (and trimmed) old docs for Enumerable#inject. 2009-09-08 00:52:29 +02:00
tjcrowder 1c0d5c9a65 doc: merged old docs for Enumerable#inGroupsOf. 2009-09-08 00:39:41 +02:00
tjcrowder df8ebd727f doc: merge old docs for Enumerable#include. 2009-09-08 00:35:16 +02:00
tjcrowder ed5456dae0 doc: merged old docs for Enumerable#grep. 2009-09-08 00:33:35 +02:00
tjcrowder 064f673f4b doc: merged old docs for Enumerable#reject, used new example. 2009-09-08 00:25:54 +02:00
tjcrowder e5c6fb4272 doc: merged old docs for Enumerable#findAll, updated and trimmed examples 2009-09-08 00:24:10 +02:00
tjcrowder adee4abe0c doc: merged old docs for enumerable#eachSlice 2009-09-08 00:21:46 +02:00
tjcrowder 45d7e79f4b doc: update docs for Enumerable#each. 2009-09-08 00:11:48 +02:00
tjcrowder 23ebc851b1 doc: merge old docs for Enumerable#detect, add example 2009-09-07 21:43:24 +02:00
tjcrowder f1c8c1e266 doc: merge and update old docs for Enumerable#collect 2009-09-07 21:42:06 +02:00
tjcrowder 061321d17c doc: merge old docs for Enumerable#any 2009-09-07 21:38:31 +02:00
tjcrowder 496503a933 doc: merged and cleaned up old docs for Enumerable#all 2009-09-07 21:37:11 +02:00
Tobie Langel a7e8e9aa7d doc: ported and updated old doc for Date#toJSON. 2009-09-07 21:31:45 +02:00
tjcrowder 12b74e6644 doc: minor updates to Class.create and Class.addMethods based on old doc. 2009-09-07 19:39:20 +02:00
tjcrowder d1cc04c911 doc: updated Array#indexOf to say that it uses strict equality 2009-09-07 19:35:04 +02:00
tjcrowder 6b24170e68 doc: ported and updated old doc for Array#without 2009-09-07 19:34:33 +02:00
tjcrowder a3eaa0401f doc: ported and updated old doc for Array#uniq 2009-09-07 19:33:27 +02:00
tjcrowder e98c87d681 doc: ported old doc example for Array#toJSON 2009-09-07 19:32:45 +02:00
tjcrowder d9f0a15dd5 doc: very minor clarification of Array#size 2009-09-07 19:31:59 +02:00
Tobie Langel a1ab78858f Minor syntactic correction in Array#reverse. 2009-09-07 19:31:16 +02:00
Tobie Langel 83ad9b622b doc: ported and updated old docs for Array#reverse. 2009-09-07 19:29:04 +02:00
tjcrowder 029197572b doc: ported old docs for Array#inspect, but dropped comment about Array#join 2009-09-07 18:57:12 +02:00
tjcrowder 70dbcf39eb doc: port old docs for Array#indexOf, update examples slightly 2009-09-07 18:56:24 +02:00
tjcrowder 798a367a3d doc: port old Array#flatten docs and clarify 2009-09-07 18:55:31 +02:00
tjcrowder a6dac432ac doc: port old Array#compact docs and clarify 2009-09-07 18:52:35 +02:00
tjcrowder 7fcd513364 doc: clarify Array#first and Array#last [#295 state:resolved] 2009-09-07 18:52:03 +02:00
tjcrowder 53317eae50 doc: port old docs for Array#clear 2009-09-07 18:51:06 +02:00
tjcrowder 1bdaaa5f49 doc: minor grammar fix in array overview 2009-09-07 18:49:37 +02:00
Tobie Langel d98e5c1372 Amend README file. 2009-09-03 01:52:46 +02:00
Tobie Langel 0a8cf9b6a3 Prevent Rakefile from mishandling missing gem errors. 2009-09-02 18:38:31 +02:00
Tobie Langel f8cffdc80b Updated PDoc submodule. 2009-09-02 15:54:54 +02:00
Tobie Langel f405b2c510 Bump version to 1.6.1. 2009-08-24 00:58:06 +02:00
Tobie Langel 94747c579f Avoid triggering a warning when Java is disabled in IE8. [#668 state:resolved] 2009-08-23 23:24:36 +02:00
Tobie Langel 4cd37c3d38 doc: Update to latest version of PDoc. 2009-08-23 06:24:33 +02:00
Tobie Langel a0f5af8c43 doc: Update PDoc. Empty the ruby helpers which are again part of PDoc. 2009-08-23 04:56:49 +02:00
Tobie Langel 24141e91c1 Typographic nitpicking. 2009-08-23 04:24:09 +02:00
Tobie Langel d5947d9d78 doc: Add README.markdown as index page to API documentation. 2009-08-23 04:16:23 +02:00
Tobie Langel ff5dbe343f Update to latest version of PDoc. 2009-08-23 04:12:50 +02:00
Tobie Langel 302e51e7fd Make the README file use Markdown instead of rdoc syntax. Change its extension accordingly. 2009-08-23 04:03:14 +02:00
tjcrowder 7735f545c7 doc: Updated Form.serializeElements. 2009-08-22 17:10:16 +02:00
tjcrowder b57bdbd76e doc: Fixed documentation error on Array#compact. [#295 state:resolved] 2009-08-22 17:07:57 +02:00
tjcrowder 2763ca4de7 doc: Minor modifications to Event.observe. 2009-08-22 17:01:16 +02:00
Tobie Langel 641284a233 doc: fix documentation of Event and Event#element. 2009-08-22 05:30:56 +02:00
Tobie Langel a4561bda28 doc: Fix methodized syntax. 2009-08-22 04:47:07 +02:00
tjcrowder bb20fa0763 dom: Markedly beefed up the Element#update and Element#insert documentation. [#161 state:resolved] 2009-08-22 04:44:12 +02:00
tjcrowder 93b682df47 doc: Copied Class.addMethods from current Mephisto docs and fixed minor error. [#103 state:resolved] 2009-08-22 04:37:10 +02:00
tjcrowder 63b7c5d9e8 doc: Added caveats and examples to String#stripTags and String#stripScripts [#102 state:resolved] 2009-08-22 04:36:24 +02:00
tjcrowder 67311e07fd doc: Moved scope warning from Ajax.Updater to String#evalScripts and left cross link. [#38 state:resolved] 2009-08-22 04:35:52 +02:00
tjcrowder cc478fcfbd doc: Documented Form#serialize and Form.serializeElements. [#21 state:resolved] 2009-08-22 04:34:19 +02:00
tjcrowder 41d1088160 doc: Documented Event.observe and Event.stopObserving, including vagaries about change events across browsers. [#6 state:resolved] 2009-08-22 04:32:34 +02:00
tjcrowder c57c2d270c doc: Documented Abstract.TimedObserver, Form.Observer, and Form.Element.Observer. 2009-08-22 04:30:52 +02:00
Tobie Langel 7cf1182a49 Clean-up namespace template. 2009-08-22 04:23:32 +02:00
Tobie Langel 63f96ea463 Fixed doc template to allow separate building of the documentation of each section. Usage: rake doc SECTION=lang. 2009-08-22 03:56:41 +02:00
Tobie Langel ea9f79728a Refactor Rakefile. 2009-08-22 03:29:50 +02:00
Tobie Langel 254f0e9a79 Update to latest PDoc. 2009-08-22 03:29:15 +02:00
Andrew Dupont f8ddfd5829 Update to latest PDoc. 2009-08-21 08:11:10 -05:00
Andrew Dupont f980f82d41 PDoc styling. 2009-08-21 08:11:10 -05:00
Andrew Dupont cdc4366153 Add more `Enumerable` citations in PDoc. 2009-08-21 08:11:10 -05:00
Andrew Dupont 775ea2bf36 Apply Christophe's helpers.rb bugfixes to Prototype's custom PDoc template. 2009-08-21 08:11:09 -05:00
Andrew Dupont 349187e27f Beautify frame around permalinked method. 2009-08-21 08:11:09 -05:00
Andrew Dupont e0c03655cd Fix relative path issues in PDoc template (once and for all). 2009-08-21 08:11:09 -05:00
Andrew Dupont fefc2b728d Change PDoc syntax for "methodized" methods. 2009-08-21 08:11:09 -05:00
Tobie Langel de9ba09371 Updated changelog. 2009-08-12 01:31:35 +02:00
Tobie Langel b214744a5d Greatly simplify String#(un)escapeHTML and remove their DOM dependencies. 2009-08-12 01:27:10 +02:00
Tobie Langel b039a4791c Update UnittestJS. Modifiy test template accordingly. 2009-08-12 01:25:59 +02:00
Tobie Langel b57ea83222 Remove redundant if statement in Element#readAttribute. 2009-08-12 01:23:47 +02:00
Tobie Langel d88aef3a55 Add missing semicolons. [#751 state:resolved] 2009-07-30 02:16:39 +02:00
Juriy Zaytsev 41b034b93b Improve `getClass` performance. [#747 state:resolved] 2009-07-29 15:04:39 +02:00
Tobie Langel add69978e0 Fix a ridiculous yet harmless typo in a previous commit. Add more tests for PeriodicalExecuter. 2009-07-22 16:06:17 +02:00
Samuel Lebeau fe388ebca5 Replace calls to String#gsub by String#replace internally, be more specific about which version of Safari is supported 2009-07-21 17:52:43 +02:00
Tobie Langel 49090bc957 Workaround IE's defficient handling of finally statements in PeriodicalExecuter. [#696 state:resolved] 2009-07-21 17:19:46 +02:00
Andrew Dupont e41ccba6d8 Re-throw error in otherwise-empty `catch` clause so that `PeriodicalExecuter` does not suppress exceptions. [#696 state:resolved] (Samuel Lebeau) 2009-07-18 18:51:16 +02:00
Andrew Dupont 059ce21040 Proper fix (and unit tests) for selector escaping issue. 2009-07-17 12:13:47 +02:00
Andrew Dupont f4570f39d7 Fix issue related to escaping of selectors for querySelectorAll. [#559 state:resolved] 2009-07-13 14:12:18 +02:00
Andrew Dupont 39f045d240 Rollback IFRAME error "feature test" for IE. Fix it another way. 2009-07-13 13:52:54 +02:00
Andrew Dupont 4586adff0b Bump version number. 2009-06-16 16:37:50 -05:00
Andrew Dupont 1f78f7ebd3 Remove debug code from PDoc template. 2009-06-13 19:27:39 -05:00
Andrew Dupont 16b69a2ebf Update inline documentation. 2009-06-13 19:27:22 -05:00
Andrew Dupont 068f3bba56 Create the temp directory if it doesn't exist. 2009-06-13 17:42:57 -05:00
Andrew Dupont a7eff2c0fa Avoid using `Tempfile` because Windows doesn't like it when we try to modify its access time. 2009-06-13 17:42:57 -05:00
Tobie Langel ad89bfb738 Fixed a variety of non-ASCII chars and similar [#610 state:resolved] 2009-06-11 22:48:38 +02:00
Juriy Zaytsev 6e4a3cdec8 Add Chrome 1+ to the list of supported browsers
Conflicts:

	CHANGELOG
2009-06-10 18:44:55 -04:00
Juriy Zaytsev f50dc3639d No need to recreate `subclass` function every time inheritance is being set up in `Class.create` 2009-06-10 18:32:48 -04:00
Juriy Zaytsev 78f6fad570 Simplify snippet of `_insertionTranslations.tags` extension. 2009-06-04 23:18:28 -05:00
Juriy Zaytsev b2492aeec4 Fix `Template#evaluate` "eating" previous character if `null` was returned from `toTemplateReplacements` function. 2009-06-04 23:14:05 -05:00
Juriy Zaytsev d655665fb6 Make sure (defficient) APPLET, OBJECT and EMBED elements are extended with simulated methods in IE8. Return early if _extendedByPrototype is present on an element. 2009-06-04 23:11:06 -05:00
Juriy Zaytsev 703e988745 Replace array creation and `Array#include` with a more efficient `RegExp#test` 2009-06-04 23:07:41 -05:00
Juriy Zaytsev 7d02aef6ca Reorganize the way `ElementExtensions` are defined. Make sure elements used in SpecificElementExtensions are cleaned up. 2009-06-04 23:01:47 -05:00
Juriy Zaytsev fa4314aeea No need to overengineer. 2009-06-04 22:59:54 -05:00
Andrew Dupont 1b20386115 Minor PDoc changes. 2009-06-04 22:56:55 -05:00
Andrew Dupont 483c7886de Update PDoc to latest. 2009-06-02 19:58:47 -05:00
Andrew Dupont 269653fd38 Fix typo in selector.js. 2009-06-01 16:25:32 -05:00
Juriy Zaytsev 31fe0a0328 Make sure $A works with primitive values. 2009-05-15 22:05:53 -05:00
Juriy Zaytsev cebf7d673b Do not browser sniff when forking `unmark` function in selector suite. Instead use a proper test - PROPERTIES_ATTRIBUTES_MAP. 2009-05-15 22:04:21 -05:00
Juriy Zaytsev 74374347c1 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. 2009-05-15 22:02:51 -05:00
Andrew Dupont c44a071a3f `Prototype.Browser.Opera` now uses stronger inference and is determined by [[Class]] of `window.opera` being - "Opera". (kangax) 2009-05-15 21:50:32 -05:00
Andrew Dupont cc0a75fe19 Make regex used in `stripTags` stricter. (igor, kangax) [#674 state:resolved] 2009-05-11 23:29:26 -05:00
Juriy Zaytsev cb729625ae Use native `String.prototype.trim` in `String.prototype.trim` when available. (kangax) 2009-05-11 23:23:13 -05:00
Andrew Dupont 6f6b73cc75 Fix error in event.js which prevented attaching more than one responder for an event name/element combination. [#651 state:resolved] (Rob Lineweaver) 2009-05-02 13:48:41 -05:00
Andrew Dupont 005f066f1a Fix typo in event.js. 2009-05-02 13:41:10 -05:00
Andrew Dupont 37bdd117a0 Fix JavaScript error in IE8 when viewing documentation. 2009-04-08 00:28:40 -05:00
Andrew Dupont f0872c96a6 Update to latest PDoc. 2009-04-08 00:18:33 -05:00
Andrew Dupont 7bf8bd415f Fix accidental backtick escape. 2009-04-03 12:52:45 -05:00
Andrew Dupont b748b00ce0 Updated to latest PDoc. 2009-04-03 11:20:24 -05:00
Juriy Zaytsev c480bd7b97 Do not sniff when testing for IE's proprietary mouseenter/mouseleave events support. Use more robust inference instead. 2009-03-28 19:45:33 -04:00
Andrew Dupont fd46f6bbcf Bump version number. 2009-03-27 17:59:59 -05:00
Juriy Zaytsev 6c38d84254 Revert Opera title fix 2009-03-25 01:19:28 -04:00
Juriy Zaytsev 63bb172663 Fix erroneous `readAttribute` test. 2009-03-25 00:47:54 -04:00
Juriy Zaytsev b35ebeb135 Avoid creation of element, use document.documentElement instead 2009-03-24 23:43:08 -04:00
Juriy Zaytsev 3e3b470009 Use feature testing in `Element#readAttribute` when taking care of Opera's `getAttribute('title')` quirk 2009-03-24 23:33:17 -04:00
Juriy Zaytsev d4c182c03d Use `Prototype.emptyFunction` consistently throughout unit tests. [#253 state:resolved] 2009-03-24 07:59:32 -04:00
Tobie Langel b8635ebdde deprecation extension: Modified deprecation notice for Array#reduce. 2009-03-23 23:35:13 +01:00
Tobie Langel f44d0500d9 deprecation extension: clean-up tests. 2009-03-23 16:30:28 +01:00
Tobie Langel 827e6ee1ae deprecation extension: mark Array#reduce() as removed. [#569 state:resolved] (Tobie Langel) 2009-03-23 16:06:01 +01:00
Juriy Zaytsev 0b4e142d8a `Form.serialize` now works safely with forms that have "length"-named elements. [#77 state:resolved] 2009-03-23 00:15:34 -04:00
Juriy Zaytsev 71a8663370 `Element#update` now takes care of SCRIPT elements in IE. [#573 state:resolved] 2009-03-22 19:04:04 -04:00
Juriy Zaytsev 2c986d8eaf Remove unused local variables. Fix failing form test. 2009-03-22 10:53:08 -04:00
Juriy Zaytsev fa15f212fa Fix `escapeHTML` in Chrome by using a more strict check. 2009-03-22 01:12:45 -04:00
Juriy Zaytsev dee8a1010c Fix faulty DOM test. 2009-03-21 22:23:14 -04:00
Juriy Zaytsev 0e63247604 Make `Element.extend` take care of IE8 HTMLAppletElement and HTMLObjectElement objects bug. 2009-03-21 22:10:03 -04:00
Juriy Zaytsev eaa8b949c8 Fix another DOM test to pass in IE8. 2009-03-21 21:09:55 -04:00
Andrew Dupont 9bb8fbf2ff Merge branch 'master' of git@github.com:sstephenson/prototype 2009-03-20 19:44:11 -05:00
Juriy Zaytsev 06068431f7 Fix for/htmlFor translation in IE8 2009-03-20 18:08:34 -04:00
Juriy Zaytsev 30c1935cdb Fix `writeAttribute` and `readAttribute` failures in IE8. Use proper feature testing when taking care of getAttribute/setAttribute glitches. 2009-03-20 17:33:56 -04:00
Juriy Zaytsev 25a38aa717 resolve conflict 2009-03-20 16:27:28 -04:00
Juriy Zaytsev a97c044361 Remove sniffing from one of the DOM tests, making it pass in IE8. 2009-03-20 16:26:08 -04:00
Juriy Zaytsev 4b9bf985a2 Fix Form.reset test in IE 2009-03-20 16:23:38 -04:00
Juriy Zaytsev 88d7671f0d Fix Form.reset test in IE 2009-03-20 16:10:39 -04:00
Juriy Zaytsev b34355653e Remove Array#reduce [#569 state:resolved] 2009-03-20 12:05:02 -04:00
Andrew Dupont e07dcf83df Changes to PDoc template. 2009-03-20 05:49:47 -05:00
Andrew Dupont deeb3bbd88 PDoc changes. 2009-03-20 05:46:53 -05:00
Andrew Dupont 77db1f9107 Merge branch 'master' of git@github.com:sstephenson/prototype 2009-03-20 05:44:20 -05:00
Juriy Zaytsev c7a5d3480e Make sure try/catch/finally is used instead of try/finally [#421 state:resolved] 2009-03-19 16:47:07 -04:00
Juriy Zaytsev 1a375daea2 Remove sniffing from `$A` by using `in` operator when accessing property of a nodelist. 2009-03-19 16:22:37 -04:00
Andrew Dupont e0cd7d5edf PDoc cleanup. 2009-03-10 19:08:21 -05:00
Andrew Dupont 4fefe32876 Add Element#clone as a safe wrapper of native `cloneNode`. (Andrew Dupont, kangax) 2009-03-10 12:22:56 -05:00
Andrew Dupont c2915dfe9b Add `tmp` directory to ignores. 2009-03-09 11:23:25 -05:00
Andrew Dupont 8f697f3e85 Add tests to ensure IE8 properly assigns a class name in the `Element` constructor. [#529 state:resolved] (Riki Fridrich, Andrew Dupont) 2009-03-08 20:27:38 -05:00
Andrew Dupont fa12b00111 Remove sniffing from `Element` when detecting broken `setAttribute` in IE. [#571 state:resolved] (kangax) 2009-03-08 18:18:55 -05:00
Andrew Dupont ba2c26090b Remove sniffing from `Element.update` branching in favor of feature detection. [#574 state:resolved] 2009-03-08 18:16:02 -05:00
Andrew Dupont 7833e8c909 Remove sniffing when branching `escapeHTML` and `unescapeHTML`. [#570 state:resolved] (kangax) 2009-03-08 18:02:09 -05:00
Andrew Dupont 4453e62fd5 Add the last of the documentation for the Ajax section. 2009-03-07 20:01:22 -06:00
Andrew Dupont c1a296b79b Remove debug info from PDoc template helper file. I am a putz. 2009-03-07 20:00:48 -06:00
Andrew Dupont 0b9997e155 Removed errant debug code from unit test. 2009-03-07 18:54:16 -06:00
Andrew Dupont ceae210ab6 Update PDoc to latest. 2009-03-07 15:45:40 -06:00
Andrew Dupont 15b936af91 Changes to PDoc template to fix issues relating to sections. 2009-03-07 15:45:22 -06:00
Andrew Dupont deb3df525e Change some PDoc characters to HTML entities so that we don't confuse Sprockets. 2009-03-07 15:44:50 -06:00
Andrew Dupont 0f5c9bfcc5 Updates to PDoc in DOM section. 2009-03-07 15:44:12 -06:00
Andrew Dupont 449e532f9a Add PDoc for most of Ajax section. 2009-03-07 15:43:20 -06:00
Andrew Dupont 8c2af9bd66 Descriptions of all the classes/namespaces in the "Language" section. 2009-03-06 15:41:11 -06:00
Andrew Dupont 2895be3587 Add custom PDoc HTML template to source tree. 2009-03-06 12:27:40 -06:00
Andrew Dupont 0445d37116 Update Rake "doc" task to integrate with Sprockets. 2009-03-06 12:26:50 -06:00
Andrew Dupont 205e711ebc Update to latest PDoc. 2009-03-06 12:24:29 -06:00
Andrew Dupont d189f809a4 Fixed aliases in PDoc for Enumerable. 2009-03-06 12:20:09 -06:00
Andrew Dupont f8e96818f7 Updated Sprockets and PDoc to latest versions. 2009-03-05 17:58:43 -06:00
Andrew Dupont 00742ac153 Extensive PDoc changes. 2009-03-05 13:56:01 -06:00
Andrew Dupont e88f22e0c3 Redefine Element#down in IE 6-7 to avoid extending all descendants when no selector is given. [#452 state:resolved] (eno, Andrew Dupont) 2009-02-28 12:18:31 -06:00
Andrew Dupont 4a0b9b7c1d Reverse the definitions of Event#pointer(X|Y) and Event#pointer to prevent unnecessary computation. [#403 state:resolved] (Nick Stakenburg, Andrew Dupont). 2009-02-28 12:16:12 -06:00
Andrew Dupont 9d7a981e4c Add first-class support for `mouseenter` and `mouseleave` events in non-IE browsers (IE supports them natively). [#350 state:resolved] (Nick Stakenburg, Andrew Dupont) 2009-02-28 10:07:08 -06:00
Andrew Dupont 682a55c2d3 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) 2009-02-28 04:48:57 -06:00
Andrew Dupont 26eaa4300b Fix issue where Opera 9.x returns incorrect results on certain Selector queries with descendant combinators. [#395 state:resolved] (Arpan, fearphage, kangax, Andrew Dupont) 2009-02-28 04:41:55 -06:00
Andrew Dupont ee56c93f16 Remove old lines from class.js. 2009-02-28 04:36:44 -06:00
Andrew Dupont de77550574 Minor PDoc corrections. 2009-02-27 17:02:42 -06:00
Andrew Dupont d22a9988fa 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) 2009-02-27 16:45:51 -06:00
Andrew Dupont 47abfa68f0 Ensure `toString` and `valueOf` properties are copied to a subclass only when necessary in IE6. [@382 state:resolved] (Samuel Lebeau) 2009-02-27 16:35:05 -06:00
Andrew Dupont ae667274f7 Removed two incorrect string tests. 2009-02-26 00:08:08 -06:00
Andrew Dupont d97466a7c1 Corrected a regex that was not picking up on attributes with hyphens. 2009-02-25 23:55:29 -06:00
Andrew Dupont 68096071a4 Added PDoc for form.js. 2009-02-25 17:00:40 -06:00
Andrew Dupont 8bfd9cf555 Added PDoc for event.js. 2009-02-24 05:44:04 -06:00
Andrew Dupont 60b9477c5b Added PDoc for dom.js. 2009-02-24 05:21:31 -06:00
Andrew Dupont 7a32414655 More PDoc additions. 2009-02-23 20:35:49 -06:00
Andrew Dupont 50304a7fa6 Add "doc" directory to .gitignore. 2009-02-23 20:25:20 -06:00
Andrew Dupont c9df079839 Added/corrected some PDoc. 2009-02-23 20:21:02 -06:00
Andrew Dupont 043653a282 Make sure `getAttribute` is used without flag when accessing the "type" attribute of an iframe (IE throws error otherwise). [#118 state:resolved] (Zekid, kangax) 2009-02-23 20:20:21 -06:00
Andrew Dupont e9bdaef0af String#gsub should escape RegExp metacharacters when the first argument is a string. [#469 state:resolved] (michael, kangax) 2009-02-23 20:13:41 -06:00
Andrew Dupont e3845ba0f6 Fix order of replacement in String#unescapeHTML [#544 state:resolved] (SWeini, kangax) 2009-02-23 20:07:43 -06:00
Andrew Dupont 432a9422d6 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) 2009-02-22 15:59:01 -06:00
Andrew Dupont 8fe518719e Fix Selector to match elements with attributes containing hyphens. [#285 state:resolved] (leiyou, jddalton, kangax) 2009-02-22 15:16:19 -06:00
Andrew Dupont dc9d274d89 Make sure Form.reset always returns a reference to the receiver element. [#309 state:resolved] (Phil, kangax) 2009-02-22 15:11:40 -06:00
Andrew Dupont 97fc24a0b2 Escape ":" and "." characters when doing contextual CSS selection in browsers that support querySelectorAll. [#559 state:resolved] (fxtentacle, Andrew Dupont) 2009-02-22 12:45:00 -06:00
Andrew Dupont 9da5045df2 Removed redundant lines in selector.js. [#138 state:resolved] (Richard Quadling) 2009-02-21 23:32:00 -06:00
Andrew Dupont 91b787ae17 Ensure the `target` property on events is never undefined in IE. [#383 state:resolved] 2009-02-21 23:27:18 -06:00
Andrew Dupont ab1313ea20 Ensure Element#descendants always returns an array. [#373 state:resolved] 2009-02-18 10:34:01 -06:00
Andrew Dupont d3189652a6 Fixed incorrect usage of === operator. [#127 state:resolved] 2009-02-18 10:30:19 -06:00
Sam Stephenson 69adbbbf8f Update to the latest Sprockets, which trims trailing whitespace from source lines 2009-02-16 12:13:13 -06:00
Sam Stephenson 6fb6308744 Update vendor/sprockets so Prototype builds on Windows again 2009-02-15 21:53:12 -06:00
sstephenson 463141edc5 Update CHANGELOG 2009-02-10 19:20:19 -07:00
James Wheare 03512f5060 Fix for http://prototype.lighthouseapp.com/projects/8886/tickets/543. Don't switch fixed position elements to absolute in Element.getDimensions
Signed-off-by: Sam Stephenson <sam@37signals.com>
2009-02-11 10:14:15 +08:00
Samuel Lebeau ceb7b72621 Avoid String#sub with empty pattern to make endless loop [#534 state:resolved]
Signed-off-by: Sam Stephenson <sam@37signals.com>
2009-02-11 10:12:28 +08:00
Sam Stephenson 9bb3d7517d Update CHANGELOG 2009-02-10 11:26:14 -06:00
Sam Stephenson 6d706084ac Update to Sprockets 0.9.0 2009-02-10 11:18:24 -06:00
Sam Stephenson 938ac16c2d Load PROTOTYPE_VERSION from src/constants.yml 2009-02-10 10:52:08 -06:00
Sam Stephenson a519c02b2e Sprocketize Prototype 2009-01-27 15:42:32 -06:00
Sam Stephenson d5be195340 Add sprockets as a submodule 2009-01-27 13:32:57 -06:00
Andrew Dupont 952feb48a3 Changes to dom.js to get direct extension of element prototypes working in IE8. We're down to one test failure in the dom.js unit tests. 2009-01-21 20:40:45 -06:00
Andrew Dupont 4af7dc0f5d Changed feature detection in order to properly detect the mutable Element prototypes in IE8. 2009-01-21 20:29:23 -06:00
Andrew Dupont 44287477cc Updated Rakefile to check for presence of PDoc before running 'doc' task. 2009-01-12 09:00:42 -06:00
Andrew Dupont 9bcf687751 Update PDoc submodule to latest version. 2009-01-12 08:51:02 -06:00
Samuel Lebeau e819a241e2 Fix Rakefile for PDoc generation 2009-01-12 08:28:59 -06:00
Samuel Lebeau 8a999a0389 Fix Range documentation 2009-01-12 08:28:49 -06:00
Samuel Lebeau b2597ece0c Start PDoc integration. 2009-01-12 08:27:43 -06:00
Andrew Dupont 24fb692281 Added PDoc as a git submodule. 2009-01-11 18:36:25 -06:00
Andrew Dupont 997689fcea Switch Object.is(Array|String|Number) to use the vastly-superior approach discovered by Juriy. 2009-01-11 18:26:26 -06:00
savetheclocktower 31d1c6fd48 Further fix to ensure Object#is(String|Number) do not throw exceptions on host objects in IE. [#510 state:resolved] 2009-01-05 10:49:09 -06:00
Andrew Dupont 9f5c40c744 Ensure Enumerable#grep can handle strings with RegExp metacharacters. [#257 state:resolved] 2008-12-19 18:50:08 -06:00
Andrew Dupont e9e8c7fbe5 Switch to the "doScroll approach" for the dom:loaded custom event. [#127 state:resolved] 2008-12-16 22:52:24 -06:00
savetheclocktower bd1d3fa1ff Change the way we detect that an object of key/value pairs has been passed to Element#store. 2008-12-16 13:19:23 -06:00
savetheclocktower 0549952094 I suppose we owe it to our contributors to spell their names right. 2008-12-16 13:16:07 -06:00
Andrew Dupont c3c953363b Optimize document.viewport.get(Dimensions|Width|Height). [#336 state:resolved] 2008-12-15 20:40:15 -06:00
Andrew Dupont 07506e648b Fix issue where Object#isString and Object#isNumber return false for String and Number "wrapper" objects. [#375 state:resolved] 2008-12-15 20:31:18 -06:00
Andrew Dupont c493d09b7b Fix typo in CHANGELOG. 2008-12-15 20:21:44 -06:00
Andrew Dupont 60a6c7ac70 Set document.loaded = true before firing dom:loaded custom event. 2008-12-15 20:21:17 -06:00
savetheclocktower c4f6066d2c Allow Element#store to accept an object containing several key/value pairs. 2008-12-15 09:51:16 -06:00
savetheclocktower 1ca385b18c Change Element#retrieve to return the element itself (for chaining). 2008-12-15 09:41:10 -06:00
savetheclocktower f8a25223d0 Add tests for Element#retrieve. 2008-12-15 09:37:12 -06:00
Tobie Langel 2403e3e8ce Remove useless base_test fixtures. 2008-12-15 00:46:18 +01:00
Andrew Dupont c4c7bf63db Update CHANGELOG. 2008-12-14 03:54:18 -06:00
Andrew Dupont f6a2cdb067 Merge branch 'storage' 2008-12-14 03:51:35 -06:00
Andrew Dupont 0f2bbafd11 Remove redundant tests from base_test.js. 2008-12-14 03:40:27 -06:00
Andrew Dupont bc899339a9 Remove redundant tests from base_test.js. 2008-12-14 03:40:15 -06:00
Andrew Dupont 3b21105237 Alter event system to use new element storage API rather than have its own global hashtable. 2008-12-14 01:35:49 -06:00
Andrew Dupont 77b9a2614a Alter element storage API to handle the `window` object gracefully. 2008-12-14 01:35:12 -06:00
Andrew Dupont 3977e66796 Added Element#store and Element#retrieve for safe, hash-backed storage of element metadata (no memory leaks). Also added Element#getStorage for working with the element's storage hash directly. Hat tip: Mootools. 2008-12-12 23:53:39 -06:00
Tobie Langel 99f74b526a Add caja_builder for optional cajoled tests. 2008-12-12 13:55:28 +01:00
Tobie Langel 52a781ae2a Switch to UnittestJS. 2008-12-11 18:01:31 +01:00
Tobie Langel 1d617df4df Avoid using arguments.callee in string.js. 2008-12-11 17:05:24 +01:00
Tobie Langel 1cda280a72 Avoid using arguments.callee in dom.js. 2008-12-11 17:05:09 +01:00
Tobie Langel b74eeeb757 Make Element constructor creation cleaner. 2008-12-11 17:04:13 +01:00
Tobie Langel 51d0181d29 Avoid using arguments.callee in events.js. 2008-12-11 17:03:53 +01:00
Tobie Langel 7f6b3e81a2 Overwrite Array#concat method for handling the arguments object in Opera. 2008-12-11 17:03:10 +01:00
Tobie Langel 1395330440 Fix Array errors in Opera. 2008-12-11 17:00:45 +01:00
Tobie Langel e5f7cac36f Avoid shadowing named functions. 2008-12-11 16:55:02 +01:00
Tobie Langel 86407790d2 Fix issue where certain versions of Safari treat class names
case-insensitively in Selector/7390 queries. [#390 state:resolved]
2008-12-11 12:07:23 +01:00
Andrew Dupont 1324e4abe0 Refactored class.js. 2008-12-11 12:06:22 +01:00
Tobie Langel 2525b21be2 Reorganized unit tests to match the file structure of the source. 2008-12-11 12:06:00 +01:00
Andrew Dupont 54bf343560 Fix issue where Function#argumentNames returned incorrect results in IE when comments were intermixed with argument names. [#397 state:resolved] 2008-12-11 12:02:26 +01:00
savetheclocktower c039f68fb7 Selector.patterns should be represented as an ordered structure. [#315 state:resolved] 2008-12-11 12:00:44 +01:00
Tobie Langel dad88f6ebf Refactored event.js. 2008-12-11 11:56:38 +01:00
Tobie Langel 1aacbd692c string.js rewrite. 2008-12-11 11:50:51 +01:00
Tobie Langel 535d4e6d18 function.js rewrite. 2008-12-11 11:49:41 +01:00
Tobie Langel 0b646c01c6 Refactor object.js. 2008-12-11 11:46:42 +01:00
Tobie Langel 48f5ff2072 Split ajax.js. 2008-12-11 11:45:10 +01:00
Tobie Langel fe35c941eb Split src code into lang, dom and ajax directories. 2008-12-11 11:43:15 +01:00
Tobie Langel 6850979395 Split base.js up. 2008-12-11 11:42:15 +01:00
Tobie Langel 616319377f Extract the template class out of string.js and into its own file. 2008-12-11 11:35:20 +01:00
Tobie Langel 314e161c57 Reorganize range.js. 2008-12-11 11:34:19 +01:00
Tobie Langel 23b760865a Reorganize number.js. 2008-12-11 11:34:05 +01:00
Tobie Langel 75440aa98d Reorganize hash.js. 2008-12-11 11:33:45 +01:00
Tobie Langel 92eac6f1f6 Reorganize enumerable.js. 2008-12-11 11:33:31 +01:00
Tobie Langel d3e833c48e Reorganizing array.js source code. 2008-12-11 11:31:36 +01:00
94 changed files with 11906 additions and 5557 deletions

6
.gitignore vendored
View File

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

20
.gitmodules vendored Normal file
View File

@ -0,0 +1,20 @@
[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

180
CHANGELOG
View File

@ -1,3 +1,179 @@
* 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)
* Add support for the Chrome browser in jstest.rb. (Andrew Dupont)
@ -182,13 +358,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 Stackenburg, Andrew Dupont]
* Optimize Element#up/down/next/previous. Closes #10353. [Dylan Bruzenak, Nick Stakenburg, 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 Stackenburg]
* Change document.viewport.getDimensions to exclude scrollbars in all cases. Closes #10148, #9288. [Nick Stakenburg]
* Add logic to Element#getStyle in Opera that fixes inaccurate reporting of computed 'width' and 'height' properties. [Andrew Dupont]

63
README.markdown Normal file
View File

@ -0,0 +1,63 @@
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>.

View File

@ -1,56 +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 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,43 +1,183 @@
require 'rake'
require 'rake/packagetask'
require 'yaml'
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'
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
task :default => [:dist, :dist_helper, :package, :clean_package_source]
desc "Builds the distribution."
task :dist do
$:.unshift File.join(PROTOTYPE_ROOT, 'lib')
require 'protodoc'
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
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
task :require do
PrototypeHelper.require_pdoc
end
end
task :doc => ['doc:build']
desc "Builds the updating helper."
task :dist_helper do
$:.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
PrototypeHelper.sprocketize(:path => 'ext/update_helper', :source => 'prototype_update_helper.js')
end
Rake::PackageTask.new('prototype', PROTOTYPE_VERSION) do |package|
Rake::PackageTask.new('prototype', PrototypeHelper::VERSION) do |package|
package.need_tar_gz = true
package.package_dir = PROTOTYPE_PKG_DIR
package.package_dir = PrototypeHelper::PKG_DIR
package.package_files.include(
'[A-Z]*',
'dist/prototype.js',
@ -47,46 +187,87 @@ Rake::PackageTask.new('prototype', PROTOTYPE_VERSION) do |package|
)
end
desc "Builds the distribution and the test suite, runs the tests and collects their results."
task :test => [:dist, :test_units]
task :clean_package_source do
rm_rf File.join(PrototypeHelper::PKG_DIR, "prototype-#{PrototypeHelper::VERSION}")
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)
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
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
%w( safari firefox ie konqueror opera chrome ).each do |browser|
t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser)
task :build => [:clean, :dist] do
builder = UnittestJS::Builder::SuiteBuilder.new({
:input_dir => PrototypeHelper::TEST_UNIT_DIR,
:assets_dir => PrototypeHelper::DIST_DIR
})
selected_tests = (ENV['TESTS'] || '').split(',')
builder.collect(*selected_tests)
builder.render
end
task :clean => [:require] do
UnittestJS::Builder.empty_dir!(PrototypeHelper::TMP_DIR)
end
task :require do
PrototypeHelper.require_unittest_js
end
end
task :test_units do
puts '"rake test_units" is deprecated. Please use "rake test" instead.'
end
task :build_unit_tests do
Dir[File.join('test', 'unit', '*_test.js')].each do |file|
PageBuilder.new(file, 'prototype.erb').render
end
puts '"rake test_units" is deprecated. Please use "rake test:build" instead.'
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 '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)
puts '"rake clean_tmp" is deprecated. Please use "rake test:clean" instead.'
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

3
dist/.gitignore vendored
View File

@ -1 +1,2 @@
prototype.js
prototype.js
prototype_update_helper.js

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="../../test/lib/unittest.js" type="text/javascript"></script>
<link rel="stylesheet" href="../../test/test.css" type="text/css" />
<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" />
</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.push(null);
log.type.push(null);
log.message.length = 0;
log.type.length = 0;
},
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.push(null);
log.type.push(null);
log.message.length = 0;
log.type.length = 0;
},
assertWarnNotified: function(expected) {
@ -63,21 +63,30 @@
var actualType = log.type.beforeLast() || log.type.last();
this.assertEqual(expected, actualMessage, 'assertWarnNotified');
this.assertEqual('warn', actualType, 'assertWarnNotified');
log.message.push(null);
log.type.push(null);
log.message.length = 0;
log.type.length = 0;
},
assertNotNotified: function() {
this.assertNull(log.message.last(), 'assertNotNotified');
this.assertNull(log.type.last());
log.message.push(null);
log.type.push(null);
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 || '');
}
});
new Test.Unit.Runner({
testGetStack: function() {
this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack());
var stack = prototypeUpdateHelper.getStack();
if (stack === '') {
this.info('UpdaterHelper#getStack is currently not supported on this browser.')
} else {
this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack());
}
},
testDisplay: function() {
@ -259,6 +268,15 @@
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' +
@ -267,6 +285,38 @@
this.assertNotNotified();
},
testSelectorInstanceMethods: function() {
var selector = new Selector('div');
this.assertWarnNotified('The Selector class has been deprecated. Please use the new Prototype.Selector API instead.');
selector.findElements(document);
this.assertWarnNotified('Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.');
selector.match(document.documentElement);
this.assertWarnNotified('Selector#match has been deprecated. Please use the new Prototype.Selector API instead.');
selector.toString();
this.assertWarnNotified('Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.');
selector.inspect();
this.assertWarnNotified('Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.');
},
testSelectorMatchElements: function() {
Selector.matchElements([], 'div');
this.assertWarnNotified('Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.');
},
testSelectorFindElement: function() {
Selector.findElement([], 'div');
this.assertWarnNotified('Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.');
},
testSelectorFindChildElements: function() {
Selector.findChildElements(document, 'div');
this.assertWarnNotified('Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.');
},
testLogDeprecationOption: function() {
prototypeUpdateHelper.logLevel = UpdateHelper.Warn;
var h = $H({ foo: 2 });

View File

@ -1,6 +1,6 @@
<%= include "update_helper.js" %>
//= require "update_helper"
/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008 Tobie Langel
/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008-2009 Tobie Langel
*
* UpdateHelper for Prototype is freely distributable under the same
* terms as Prototype (MIT-style license).
@ -17,7 +17,7 @@
*
* This, for example, will prevent deprecation messages from being logged.
*
* THIS SCRIPT WORKS IN FIREFOX ONLY
* THIS SCRIPT DOES NOT WORK IN INTERNET EXPLORER
*--------------------------------------------------------------------------*/
var prototypeUpdateHelper = new UpdateHelper([
@ -253,6 +253,15 @@ 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,
@ -266,6 +275,62 @@ var prototypeUpdateHelper = new UpdateHelper([
message: 'The class API has been fully revised and now allows for mixins and inheritance.\n' +
'You can find more about it here: http://prototypejs.org/learn/class-inheritance',
condition: function() { return !arguments.length }
},
{
methodName: 'initialize',
namespace: Selector.prototype,
message: 'The Selector class has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'findElements',
namespace: Selector.prototype,
message: 'Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'match',
namespace: Selector.prototype,
message: 'Selector#match has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'toString',
namespace: Selector.prototype,
message: 'Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'inspect',
namespace: Selector.prototype,
message: 'Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'matchElements',
namespace: Selector,
message: 'Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'findElement',
namespace: Selector,
message: 'Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
},
{
methodName: 'findChildElements',
namespace: Selector,
message: 'Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.',
type: 'warn'
}
]);
@ -290,6 +355,7 @@ var prototypeUpdateHelper = new UpdateHelper([
}
function defineSetters(obj, prop) {
storeProperties(obj);
if (obj.__properties.include(prop)) return;
obj.__properties.push(prop);
obj.__defineGetter__(prop, function() {
@ -303,6 +369,7 @@ var prototypeUpdateHelper = new UpdateHelper([
}
function checkProperties(hash) {
storeProperties(hash);
var current = Object.keys(hash);
if (current.length == hash.__properties.length)
return;
@ -313,6 +380,12 @@ 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);
@ -335,7 +408,7 @@ var prototypeUpdateHelper = new UpdateHelper([
});
Hash.prototype.initialize = Hash.prototype.initialize.wrap(function(proceed, object) {
this.__properties = __properties.clone();
storeProperties(this);
for (var prop in object) defineSetters(this, prop);
proceed(object);
});

View File

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

View File

@ -1,36 +0,0 @@
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.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

View File

@ -1,7 +0,0 @@
/* 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,391 +1,116 @@
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
function() {return new ActiveXObject('Microsoft.XMLHTTP')}
) || false;
},
activeRequestCount: 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.
*
**/
Ajax.Responders = {
responders: [],
_each: function(iterator) {
this.responders._each(iterator);
},
register: function(responder) {
if (!this.include(responder))
this.responders.push(responder);
},
unregister: function(responder) {
this.responders = this.responders.without(responder);
},
dispatch: function(callback, request, transport, json) {
this.each(function(responder) {
if (Object.isFunction(responder[callback])) {
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) { }
}
});
}
};
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate: function() { Ajax.activeRequestCount++ },
onComplete: function() { Ajax.activeRequestCount-- }
});
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: '',
evalJSON: true,
evalJS: true
};
Object.extend(this.options, options || { });
this.options.method = this.options.method.toLowerCase();
if (Object.isString(this.options.parameters))
this.options.parameters = this.options.parameters.toQueryParams();
else if (Object.isHash(this.options.parameters))
this.options.parameters = this.options.parameters.toObject();
}
});
Ajax.Request = Class.create(Ajax.Base, {
_complete: false,
initialize: function($super, url, options) {
$super(options);
this.transport = Ajax.getTransport();
this.request(url);
},
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.clone(this.options.parameters);
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params['_method'] = this.method;
this.method = 'post';
}
this.parameters = params;
if (params = Object.toQueryString(params)) {
// when GET, append parameters to URL
if (this.method == 'get')
this.url += (this.url.include('?') ? '&' : '?') + params;
else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
params += '&_=';
}
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.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
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.
*/
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)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
for (var name in headers)
this.transport.setRequestHeader(name, headers[name]);
},
success: function() {
var status = this.getStatus();
return !status || (status >= 200 && status < 300);
},
getStatus: function() {
try {
return this.transport.status || 0;
} catch (e) { return 0 }
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + response.status]
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(response, response.headerJSON);
} catch (e) {
this.dispatchException(e);
}
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
|| (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
} 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({
protocol: location.protocol,
domain: document.domain,
port: location.port ? ':' + location.port : ''
}));
},
getHeader: function(name) {
try {
return this.transport.getResponseHeader(name) || null;
} catch (e) { return null }
},
evalResponse: function() {
try {
return eval((this.transport.responseText || '').unfilterJSON());
} catch (e) {
this.dispatchException(e);
}
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
}
});
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
Ajax.Response = Class.create({
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) {
this.status = this.getStatus();
this.statusText = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON = this._getHeaderJSON();
}
if(readyState == 4) {
var xml = transport.responseXML;
this.responseXML = Object.isUndefined(xml) ? null : xml;
this.responseJSON = this._getResponseJSON();
}
},
status: 0,
statusText: '',
getStatus: Ajax.Request.prototype.getStatus,
getStatusText: function() {
try {
return this.transport.statusText || '';
} catch (e) { return '' }
},
getHeader: Ajax.Request.prototype.getHeader,
getAllHeaders: function() {
try {
return this.getAllResponseHeaders();
} catch (e) { return null }
},
getResponseHeader: function(name) {
return this.transport.getResponseHeader(name);
},
getAllResponseHeaders: function() {
return this.transport.getAllResponseHeaders();
},
_getHeaderJSON: function() {
var json = this.getHeader('X-JSON');
if (!json) return null;
json = decodeURIComponent(escape(json));
try {
return json.evalJSON(this.request.options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
},
_getResponseJSON: function() {
var options = this.request.options;
if (!options.evalJSON || (options.evalJSON != 'force' &&
!(this.getHeader('Content-type') || '').include('application/json')) ||
this.responseText.blank())
return null;
try {
return this.responseText.evalJSON(options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
}
});
Ajax.Updater = Class.create(Ajax.Request, {
initialize: function($super, container, url, options) {
this.container = {
success: (container.success || container),
failure: (container.failure || (container.success ? null : container))
};
options = Object.clone(options);
var onComplete = options.onComplete;
options.onComplete = (function(response, json) {
this.updateContent(response.responseText);
if (Object.isFunction(onComplete)) onComplete(response, json);
}).bind(this);
$super(url, options);
},
updateContent: function(responseText) {
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)) {
var insertion = { }; insertion[options.insertion] = responseText;
receiver.insert(insertion);
}
else options.insertion(receiver, responseText);
}
else receiver.update(responseText);
}
}
});
Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
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;
this.start();
},
start: function() {
this.options.onComplete = this.updateComplete.bind(this);
this.onTimerEvent();
},
stop: function() {
this.updater.options.onComplete = undefined;
clearTimeout(this.timer);
(this.onComplete || Prototype.emptyFunction).apply(this, arguments);
},
updateComplete: function(response) {
if (this.options.decay) {
this.decay = (response.responseText == this.lastText ?
this.decay * this.options.decay : 1);
this.lastText = response.responseText;
}
this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
},
onTimerEvent: function() {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});
//= require "ajax/ajax"
//= require "ajax/responders"
//= require "ajax/base"
//= require "ajax/request"
//= require "ajax/response"
//= require "ajax/updater"
//= require "ajax/periodical_updater"

21
src/ajax/ajax.js Normal file
View File

@ -0,0 +1,21 @@
/** section: Ajax
* Ajax
**/
var Ajax = {
getTransport: function() {
return Try.these(
function() {return new XMLHttpRequest()},
function() {return new ActiveXObject('Msxml2.XMLHTTP')},
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
};

22
src/ajax/base.js Normal file
View File

@ -0,0 +1,22 @@
// Abstract class; does not need documentation.
Ajax.Base = Class.create({
initialize: function(options) {
this.options = {
method: 'post',
asynchronous: true,
contentType: 'application/x-www-form-urlencoded',
encoding: 'UTF-8',
parameters: '',
evalJSON: true,
evalJS: true
};
Object.extend(this.options, options || { });
this.options.method = this.options.method.toLowerCase();
if (Object.isString(this.options.parameters))
this.options.parameters = this.options.parameters.toQueryParams();
else if (Object.isHash(this.options.parameters))
this.options.parameters = this.options.parameters.toObject();
}
});

View File

@ -0,0 +1,111 @@
/** 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;
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);
(this.onComplete || Prototype.emptyFunction).apply(this, arguments);
},
updateComplete: function(response) {
if (this.options.decay) {
this.decay = (response.responseText == this.lastText ?
this.decay * this.options.decay : 1);
this.lastText = response.responseText;
}
this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
},
onTimerEvent: function() {
this.updater = new Ajax.Updater(this.container, this.url, this.options);
}
});

271
src/ajax/request.js Normal file
View File

@ -0,0 +1,271 @@
/** 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();
this.request(url);
},
request: function(url) {
this.url = url;
this.method = this.options.method;
var params = Object.clone(this.options.parameters);
if (!['get', 'post'].include(this.method)) {
// simulate other verbs over post
params['_method'] = this.method;
this.method = 'post';
}
this.parameters = params;
if (params = Object.toQueryString(params)) {
// when GET, append parameters to URL
if (this.method == 'get')
this.url += (this.url.include('?') ? '&' : '?') + params;
else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
params += '&_=';
}
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.options.asynchronous);
if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
this.transport.onreadystatechange = this.onStateChange.bind(this);
this.setRequestHeaders();
this.body = this.method == 'post' ? (this.options.postBody || params) : null;
this.transport.send(this.body);
/* Force Firefox to handle ready state 4 for synchronous requests */
if (!this.options.asynchronous && this.transport.overrideMimeType)
this.onStateChange();
}
catch (e) {
this.dispatchException(e);
}
},
onStateChange: function() {
var readyState = this.transport.readyState;
if (readyState > 1 && !((readyState == 4) && this._complete))
this.respondToReadyState(this.transport.readyState);
},
setRequestHeaders: function() {
var headers = {
'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,
'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
};
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.
*/
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)
headers[extras[i]] = extras[i+1];
else
$H(extras).each(function(pair) { headers[pair.key] = pair.value });
}
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 }
},
respondToReadyState: function(readyState) {
var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
if (state == 'Complete') {
try {
this._complete = true;
(this.options['on' + response.status]
|| this.options['on' + (this.success() ? 'Success' : 'Failure')]
|| Prototype.emptyFunction)(response, response.headerJSON);
} catch (e) {
this.dispatchException(e);
}
var contentType = response.getHeader('Content-type');
if (this.options.evalJS == 'force'
|| (this.options.evalJS && this.isSameOrigin() && contentType
&& contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
this.evalResponse();
}
try {
(this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
} 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({
protocol: location.protocol,
domain: document.domain,
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; }
},
evalResponse: function() {
try {
return eval((this.transport.responseText || '').unfilterJSON());
} catch (e) {
this.dispatchException(e);
}
},
dispatchException: function(exception) {
(this.options.onException || Prototype.emptyFunction)(this, exception);
Ajax.Responders.dispatch('onException', this, exception);
}
});
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

121
src/ajax/responders.js Normal file
View File

@ -0,0 +1,121 @@
/** 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])) {
try {
responder[callback].apply(responder, [request, transport, json]);
} catch (e) { }
}
});
}
};
Object.extend(Ajax.Responders, Enumerable);
Ajax.Responders.register({
onCreate: function() { Ajax.activeRequestCount++ },
onComplete: function() { Ajax.activeRequestCount-- }
});

170
src/ajax/response.js Normal file
View File

@ -0,0 +1,170 @@
/** 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) {
this.status = this.getStatus();
this.statusText = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON = this._getHeaderJSON();
}
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 }
},
/**
* 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;
json = decodeURIComponent(escape(json));
try {
return json.evalJSON(this.request.options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
},
_getResponseJSON: function() {
var options = this.request.options;
if (!options.evalJSON || (options.evalJSON != 'force' &&
!(this.getHeader('Content-type') || '').include('application/json')) ||
this.responseText.blank())
return null;
try {
return this.responseText.evalJSON(options.sanitizeJSON ||
!this.request.isSameOrigin());
} catch (e) {
this.request.dispatchException(e);
}
}
});

127
src/ajax/updater.js Normal file
View File

@ -0,0 +1,127 @@
/** 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),
failure: (container.failure || (container.success ? null : container))
};
options = Object.clone(options);
var onComplete = options.onComplete;
options.onComplete = (function(response, json) {
this.updateContent(response.responseText);
if (Object.isFunction(onComplete)) onComplete(response, json);
}).bind(this);
$super(url, options);
},
updateContent: function(responseText) {
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)) {
var insertion = { }; insertion[options.insertion] = responseText;
receiver.insert(insertion);
}
else options.insertion(receiver, responseText);
}
else receiver.update(responseText);
}
}
});

View File

@ -1,154 +0,0 @@
function $A(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
var length = iterable.length || 0, results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
if (Prototype.Browser.WebKit) {
$A = function(iterable) {
if (!iterable) return [];
// 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;
};
}
Array.from = $A;
Object.extend(Array.prototype, Enumerable);
if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
Object.extend(Array.prototype, {
_each: function(iterator) {
for (var i = 0, length = this.length; i < length; i++)
iterator(this[i]);
},
clear: function() {
this.length = 0;
return this;
},
first: function() {
return this[0];
},
last: function() {
return this[this.length - 1];
},
compact: function() {
return this.select(function(value) {
return value != null;
});
},
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(Object.isArray(value) ?
value.flatten() : [value]);
});
},
without: function() {
var values = $A(arguments);
return this.select(function(value) {
return !values.include(value);
});
},
reverse: function(inline) {
return (inline !== false ? this : this.toArray())._reverse();
},
reduce: function() {
return this.length > 1 ? this : this[0];
},
uniq: function(sorted) {
return this.inject([], function(array, value, index) {
if (0 == index || (sorted ? array.last() != value : !array.include(value)))
array.push(value);
return array;
});
},
intersect: function(array) {
return this.uniq().findAll(function(item) {
return array.detect(function(value) { return item === value });
});
},
clone: function() {
return [].concat(this);
},
size: function() {
return this.length;
},
inspect: function() {
return '[' + this.map(Object.inspect).join(', ') + ']';
},
toJSON: function() {
var results = [];
this.each(function(object) {
var value = Object.toJSON(object);
if (!Object.isUndefined(value)) results.push(value);
});
return '[' + results.join(', ') + ']';
}
});
// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
Array.prototype._each = Array.prototype.forEach;
if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
i || (i = 0);
var length = this.length;
if (i < 0) i = length + i;
for (; i < length; i++)
if (this[i] === item) return i;
return -1;
};
if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(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;
};
Array.prototype.toArray = Array.prototype.clone;
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
return string ? string.split(/\s+/) : [];
}
if (Prototype.Browser.Opera){
Array.prototype.concat = function() {
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++) {
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;
};
}

View File

@ -1,285 +0,0 @@
/* Based on Alex Arnell's inheritance implementation. */
var Class = {
create: function() {
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; i < properties.length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = klass;
return klass;
}
};
Class.Methods = {
addMethods: function(source) {
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().first() == "$super") {
var method = value;
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments) };
})(property).wrap(method);
value.valueOf = method.valueOf.bind(method);
value.toString = method.toString.bind(method);
}
this.prototype[property] = value;
}
return this;
}
};
var Abstract = { };
Object.extend = function(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
};
Object.extend(Object, {
inspect: function(object) {
try {
if (Object.isUndefined(object)) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
},
toJSON: function(object) {
var type = typeof object;
switch (type) {
case 'undefined':
case 'function':
case 'unknown': return;
case 'boolean': return object.toString();
}
if (object === null) return 'null';
if (object.toJSON) return object.toJSON();
if (Object.isElement(object)) return;
var results = [];
for (var property in object) {
var value = Object.toJSON(object[property]);
if (!Object.isUndefined(value))
results.push(property.toJSON() + ': ' + value);
}
return '{' + results.join(', ') + '}';
},
toQueryString: function(object) {
return $H(object).toQueryString();
},
toHTML: function(object) {
return object && object.toHTML ? object.toHTML() : String.interpret(object);
},
keys: function(object) {
var keys = [];
for (var property in object)
keys.push(property);
return keys;
},
values: function(object) {
var values = [];
for (var property in object)
values.push(object[property]);
return values;
},
clone: function(object) {
return Object.extend({ }, object);
},
isElement: function(object) {
return !!(object && object.nodeType == 1);
},
isArray: function(object) {
return object != null && typeof object == "object" &&
'splice' in object && 'join' in object;
},
isHash: function(object) {
return object instanceof Hash;
},
isFunction: function(object) {
return typeof object == "function";
},
isString: function(object) {
return typeof object == "string";
},
isNumber: function(object) {
return typeof object == "number";
},
isUndefined: function(object) {
return typeof object == "undefined";
}
});
Object.extend(Function.prototype, {
argumentNames: function() {
var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
.replace(/\s+/g, '').split(',');
return names.length == 1 && !names[0] ? [] : names;
},
bind: function() {
if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
var __method = this, args = $A(arguments), object = args.shift();
return function() {
return __method.apply(object, args.concat($A(arguments)));
}
},
bindAsEventListener: function() {
var __method = this, args = $A(arguments), object = args.shift();
return function(event) {
return __method.apply(object, [event || window.event].concat(args));
}
},
curry: function() {
if (!arguments.length) return this;
var __method = this, args = $A(arguments);
return function() {
return __method.apply(this, args.concat($A(arguments)));
}
},
delay: function() {
var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
},
defer: function() {
var args = [0.01].concat($A(arguments));
return this.delay.apply(this, args);
},
wrap: function(wrapper) {
var __method = this;
return function() {
return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
}
},
methodize: function() {
if (this._methodized) return this._methodized;
var __method = this;
return this._methodized = function() {
return __method.apply(null, [this].concat($A(arguments)));
};
}
});
Date.prototype.toJSON = function() {
return '"' + this.getUTCFullYear() + '-' +
(this.getUTCMonth() + 1).toPaddedString(2) + '-' +
this.getUTCDate().toPaddedString(2) + 'T' +
this.getUTCHours().toPaddedString(2) + ':' +
this.getUTCMinutes().toPaddedString(2) + ':' +
this.getUTCSeconds().toPaddedString(2) + 'Z"';
};
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;
}
};
RegExp.prototype.match = RegExp.prototype.test;
RegExp.escape = function(str) {
return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};
/*--------------------------------------------------------------------------*/
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});

1
src/constants.yml Normal file
View File

@ -0,0 +1 @@
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,3 +184,95 @@ Element.ClassNames.prototype = {
Object.extend(Element.ClassNames.prototype, Enumerable);
/*--------------------------------------------------------------------------*/
/** deprecated, section: DOM
* class Selector
*
* A class that queries the document for elements that match a given CSS
* selector.
**/
(function() {
window.Selector = Class.create({
/** deprecated
* new Selector(expression)
* - expression (String): A CSS selector.
*
* Creates a `Selector` with the given CSS selector.
**/
initialize: function(expression) {
this.expression = expression.strip();
},
/** deprecated
* Selector#findElements(root) -> [Element...]
* - root (Element | document): A "scope" to search within. All results will
* be descendants of this node.
*
* Searches the document for elements that match the instance's CSS
* selector.
**/
findElements: function(rootElement) {
return Prototype.Selector.select(this.expression, rootElement);
},
/** deprecated
* Selector#match(element) -> Boolean
*
* Tests whether a `element` matches the instance's CSS selector.
**/
match: function(element) {
return Prototype.Selector.match(element, this.expression);
},
toString: function() {
return this.expression;
},
inspect: function() {
return "#<Selector: " + this.expression + ">";
}
});
Object.extend(Selector, {
/** deprecated
* Selector.matchElements(elements, expression) -> [Element...]
*
* Filters the given collection of elements with `expression`.
*
* The only nodes returned will be those that match the given CSS selector.
**/
matchElements: Prototype.Selector.filter,
/** deprecated
* Selector.findElement(elements, expression[, index = 0]) -> Element
* Selector.findElement(elements[, index = 0]) -> Element
*
* Returns the `index`th element in the collection that matches
* `expression`.
*
* Returns the `index`th element overall if `expression` is not given.
**/
findElement: function(elements, expression, index) {
index = index || 0;
var matchIndex = 0, element;
// Match each element individually, since Sizzle.matches does not preserve order
for (var i = 0, length = elements.length; i < length; i++) {
element = elements[i];
if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
return Element.extend(element);
}
}
},
/** deprecated
* Selector.findChildElements(element, expressions) -> [Element...]
*
* Searches beneath `element` for any elements that match the selector
* (or selectors) specified in `expressions`.
**/
findChildElements: function(element, expressions) {
var selector = expressions.toArray().join(', ');
return Prototype.Selector.select(selector, element || document);
}
});
})();

1243
src/dom.js

File diff suppressed because it is too large Load Diff

2454
src/dom/dom.js Normal file

File diff suppressed because it is too large Load Diff

824
src/dom/event.js Normal file
View File

@ -0,0 +1,824 @@
(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,
KEY_RETURN: 13,
KEY_ESC: 27,
KEY_LEFT: 37,
KEY_UP: 38,
KEY_RIGHT: 39,
KEY_DOWN: 40,
KEY_DELETE: 46,
KEY_HOME: 36,
KEY_END: 35,
KEY_PAGEUP: 33,
KEY_PAGEDOWN: 34,
KEY_INSERT: 45,
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.
var buttonMap = { 0: 1, 1: 4, 2: 2 };
_isButton = function(event, code) {
return event.button === buttonMap[code];
};
} else if (Prototype.Browser.WebKit) {
// In Safari we have to account for when the user holds down
// the "meta" key.
_isButton = function(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 1 && event.metaKey;
default: return false;
}
};
} else {
_isButton = function(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
};
}
/**
* 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);
var node = event.target, type = event.type,
currentTarget = event.currentTarget;
if (currentTarget && currentTarget.tagName) {
// Firefox screws up the "click" event when moving between radio buttons
// via arrow keys. It also screws up the "load" and "error" events on images,
// reporting the document as the target instead of the original image.
if (type === 'load' || type === 'error' ||
(type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
&& currentTarget.type === 'radio'))
node = currentTarget;
}
// Fix a Safari bug where a text node gets passed as the target of an
// anchor click rather than the anchor itself.
if (node.nodeType == Node.TEXT_NODE)
node = node.parentNode;
return Element.extend(node);
}
/**
* 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;
}
}
/**
* 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));
}
/**
* 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 };
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();
event.stopPropagation();
// Set a "stopped" property so that a custom event can be inspected
// after the fact to determine whether or not it was stopped.
event.stopped = true;
}
Event.Methods = {
isLeftClick: isLeftClick,
isMiddleClick: isMiddleClick,
isRightClick: isRightClick,
element: element,
findElement: findElement,
pointer: pointer,
pointerX: pointerX,
pointerY: pointerY,
stop: stop
};
// Compile the list of methods that get extended onto Events.
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
m[name] = Event.Methods[name].methodize();
return m;
});
if (Prototype.Browser.IE) {
function _relatedTarget(event) {
var element;
switch (event.type) {
case 'mouseover': element = event.fromElement; break;
case 'mouseout': element = event.toElement; break;
default: return null;
}
return Element.extend(element);
}
Object.extend(methods, {
stopPropagation: function() { this.cancelBubble = true },
preventDefault: function() { this.returnValue = false },
inspect: function() { return '[object Event]' }
});
// IE's method for extending events.
Event.extend = function(event, element) {
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,
relatedTarget: _relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
return Object.extend(event, methods);
};
} else {
Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
Object.extend(Event.prototype, methods);
Event.extend = Prototype.K;
}
function _createResponder(element, eventName, handler) {
// We don't set a default on the call to Element#retrieve so that we can
// handle the element's "virgin" state.
var registry = Element.retrieve(element, 'prototype_event_registry');
if (Object.isUndefined(registry)) {
// First time we've handled this element. Put it into the cache.
CACHE.push(element);
registry = Element.retrieve(element, 'prototype_event_registry', $H());
}
var respondersForEvent = registry.get(eventName);
if (Object.isUndefined(respondersForEvent)) {
respondersForEvent = [];
registry.set(eventName, respondersForEvent);
}
// Work around the issue that permits a handler to be attached more than
// once to the same element & event type.
if (respondersForEvent.pluck('handler').include(handler)) return false;
var responder;
if (eventName.include(":")) {
// Custom event.
responder = function(event) {
// If it's not a custom event, ignore it.
if (Object.isUndefined(event.eventName))
return false;
// If it's a custom event, but not the _correct_ custom event, ignore it.
if (event.eventName !== eventName)
return false;
Event.extend(event, element);
handler.call(element, event);
};
} else {
// Non-custom event.
if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
(eventName === "mouseenter" || eventName === "mouseleave")) {
// If we're dealing with mouseenter or mouseleave in a non-IE browser,
// we create a custom responder that mimics their behavior within
// mouseover and mouseout.
if (eventName === "mouseenter" || eventName === "mouseleave") {
responder = function(event) {
Event.extend(event, element);
var parent = event.relatedTarget;
while (parent && parent !== element) {
try { parent = parent.parentNode; }
catch(e) { parent = element; }
}
if (parent === element) return;
handler.call(element, event);
};
}
} else {
responder = function(event) {
Event.extend(event, element);
handler.call(element, event);
};
}
}
responder.handler = handler;
respondersForEvent.push(responder);
return responder;
}
function _destroyCache() {
for (var i = 0, length = CACHE.length; i < length; i++) {
Event.stopObserving(CACHE[i]);
CACHE[i] = null;
}
}
var CACHE = [];
// Internet Explorer needs to remove event handlers on page unload
// in order to avoid memory leaks.
if (Prototype.Browser.IE)
window.attachEvent('onunload', _destroyCache);
// Safari needs a dummy event handler on page unload so that it won't
// use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
// object when page is returned to via the back button using its bfcache.
if (Prototype.Browser.WebKit)
window.addEventListener('unload', Prototype.emptyFunction, false);
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);
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);
}
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 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);
});
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; });
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);
}
registry.set(eventName, responders.without(responder));
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) {
element = $(element);
if (Object.isUndefined(bubble))
bubble = true;
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
var event;
if (document.createEvent) {
event = document.createEvent('HTMLEvents');
event.initEvent('dataavailable', true, true);
} else {
event = document.createEventObject();
event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent)
element.dispatchEvent(event);
else
element.fireEvent(event.eventType, event);
return Event.extend(event);
}
Object.extend(Event, Event.Methods);
Object.extend(Event, {
fire: fire,
observe: observe,
stopObserving: stopObserving
});
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);
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. */
var timer;
function fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearTimeout(timer);
document.loaded = true;
document.fire('dom:loaded');
}
function checkReadyState() {
if (document.readyState === 'complete') {
document.stopObserving('readystatechange', checkReadyState);
fireContentLoadedEvent();
}
}
function pollDoScroll() {
try { document.documentElement.doScroll('left'); }
catch(e) {
timer = pollDoScroll.defer();
return;
}
fireContentLoadedEvent();
}
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
} else {
document.observe('readystatechange', checkReadyState);
if (window == top)
timer = pollDoScroll.defer();
}
// Worst-case fallback
Event.observe(window, 'load', fireContentLoadedEvent);
})();

638
src/dom/form.js Normal file
View File

@ -0,0 +1,638 @@
/** 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();
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)))) {
if (key in result) {
// a key is already present; construct an array of values
if (!Object.isArray(result[key])) result[key] = [result[key]];
result[key].push(value);
}
else result[key] = value;
}
}
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;
})
},
/**
* 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))
continue;
matchingInputs.push(Element.extend(input));
}
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;
});
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);
});
},
/**
* 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;
}
};
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) {
var value = element.getValue();
if (value != undefined) {
var pair = { };
pair[element.name] = value;
return Object.toQueryString(pair);
}
}
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();
Form.Element.Serializers[method](element, value);
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))))
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;
return element;
}
};
/*--------------------------------------------------------------------------*/
var Field = Form.Element;
/** section: DOM, related to: Form
* $F(element) -> String | Array
**/
var $F = Form.Element.Methods.getValue;
/*--------------------------------------------------------------------------*/
Form.Element.Serializers = {
input: function(element, value) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
return Form.Element.Serializers.inputSelector(element, value);
default:
return Form.Element.Serializers.textarea(element, value);
}
},
inputSelector: function(element, value) {
if (Object.isUndefined(value)) return element.checked ? element.value : null;
else element.checked = !!value;
},
textarea: function(element, value) {
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' ?
'selectOne' : 'selectMany'](element);
else {
var opt, currentValue, single = !Object.isArray(value);
for (var i = 0, length = element.length; i < length; i++) {
opt = element.options[i];
currentValue = this.optionValue(opt);
if (single) {
if (currentValue == value) {
opt.selected = true;
return;
}
}
else opt.selected = value.include(currentValue);
}
}
},
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;
}
};
/*--------------------------------------------------------------------------*/
/** 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) ?
this.lastValue != value : String(this.lastValue) != String(value)) {
this.callback(this.element, value);
this.lastValue = value;
}
}
});
/**
* 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);
}
});
/*--------------------------------------------------------------------------*/
/**
* 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) {
this.callback(this.element, value);
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 'radio':
Event.observe(element, 'click', this.onElementEvent.bind(this));
break;
default:
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);
}
});

66
src/dom/selector.js Normal file
View File

@ -0,0 +1,66 @@
/** section: DOM, related to: Prototype.Selector
* $$(expression...) -> [Element...]
*
* Returns all elements in the document that match the provided CSS selectors.
**/
window.$$ = function() {
var expression = $A(arguments).join(', ');
return Prototype.Selector.select(expression, document);
};
/**
* Prototype.Selector
*
* A namespace that acts as a wrapper around
* the choosen selector engine (Sizzle by default).
*
**/
// Implementation provided by selector engine.
/**
* Prototype.Selector.select(expression[, root = document]) -> [Element...]
* - expression (String): A CSS selector.
* - root (Element | document): A "scope" to search within. All results will
* be descendants of this node.
*
* Searches `root` for elements that match the provided CSS selector and returns an
* array of extended [[Element]] objects.
**/
// Implementation provided by selector engine.
/**
* Prototype.Selector.match(element, expression) -> Boolean
* - element (Element): a DOM element.
* - expression (String): A CSS selector.
*
* Tests whether `element` matches the CSS selector.
**/
// Implementation provided by selector engine.
/**
* Prototype.Selector.find(elements, expression[, index = 0]) -> Element
* - elements (Enumerable): a collection of DOM elements.
* - expression (String): A CSS selector.
* - index: Numeric index of the match to return, defaults to 0.
*
* Filters the given collection of elements with `expression` and returns the
* first matching element (or the `index`th matching element if `index` is
* specified).
**/
if (!Prototype.Selector.find) {
Prototype.Selector.find = function(elements, expression, index) {
if (Object.isUndefined(index)) index = 0;
var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
for (i = 0; i < length; i++) {
if (match(elements[i], expression) && index == matchIndex++) {
return Element.extend(elements[i]);
}
}
}
}

View File

@ -1,217 +0,0 @@
var $break = { };
var Enumerable = {
each: function(iterator, context) {
var index = 0;
try {
this._each(function(value) {
iterator.call(context, value, index++);
});
} catch (e) {
if (e != $break) throw e;
}
return this;
},
eachSlice: function(number, iterator, context) {
var index = -number, slices = [], array = this.toArray();
if (number < 1) return array;
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
return slices.collect(iterator, context);
},
all: function(iterator, context) {
iterator = iterator || Prototype.K;
var result = true;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
if (!result) throw $break;
});
return result;
},
any: function(iterator, context) {
iterator = iterator || Prototype.K;
var result = false;
this.each(function(value, index) {
if (result = !!iterator.call(context, value, index))
throw $break;
});
return result;
},
collect: function(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator.call(context, value, index));
});
return results;
},
detect: function(iterator, context) {
var result;
this.each(function(value, index) {
if (iterator.call(context, value, index)) {
result = value;
throw $break;
}
});
return result;
},
findAll: function(iterator, context) {
var results = [];
this.each(function(value, index) {
if (iterator.call(context, value, index))
results.push(value);
});
return results;
},
grep: function(filter, iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
if (Object.isString(filter))
filter = new RegExp(filter);
this.each(function(value, index) {
if (filter.match(value))
results.push(iterator.call(context, value, index));
});
return results;
},
include: function(object) {
if (Object.isFunction(this.indexOf))
if (this.indexOf(object) != -1) return true;
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
return found;
},
inGroupsOf: function(number, fillWith) {
fillWith = Object.isUndefined(fillWith) ? null : fillWith;
return this.eachSlice(number, function(slice) {
while(slice.length < number) slice.push(fillWith);
return slice;
});
},
inject: function(memo, iterator, context) {
this.each(function(value, index) {
memo = iterator.call(context, memo, value, index);
});
return memo;
},
invoke: function(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
},
max: function(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value >= result)
result = value;
});
return result;
},
min: function(iterator, context) {
iterator = iterator || Prototype.K;
var result;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value < result)
result = value;
});
return result;
},
partition: function(iterator, context) {
iterator = iterator || Prototype.K;
var trues = [], falses = [];
this.each(function(value, index) {
(iterator.call(context, value, index) ?
trues : falses).push(value);
});
return [trues, falses];
},
pluck: function(property) {
var results = [];
this.each(function(value) {
results.push(value[property]);
});
return results;
},
reject: function(iterator, context) {
var results = [];
this.each(function(value, index) {
if (!iterator.call(context, value, index))
results.push(value);
});
return results;
},
sortBy: function(iterator, context) {
return this.map(function(value, index) {
return {
value: value,
criteria: iterator.call(context, value, index)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}).pluck('value');
},
toArray: function() {
return this.map();
},
zip: function() {
var iterator = Prototype.K, args = $A(arguments);
if (Object.isFunction(args.last()))
iterator = args.pop();
var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
return iterator(collections.pluck(index));
});
},
size: function() {
return this.toArray().length;
},
inspect: function() {
return '#<Enumerable:' + this.toArray().inspect() + '>';
}
};
Object.extend(Enumerable, {
map: Enumerable.collect,
find: Enumerable.detect,
select: Enumerable.findAll,
filter: Enumerable.findAll,
member: Enumerable.include,
entries: Enumerable.toArray,
every: Enumerable.all,
some: Enumerable.any
});

View File

@ -1,348 +0,0 @@
if (!window.Event) var Event = { };
Object.extend(Event, {
KEY_BACKSPACE: 8,
KEY_TAB: 9,
KEY_RETURN: 13,
KEY_ESC: 27,
KEY_LEFT: 37,
KEY_UP: 38,
KEY_RIGHT: 39,
KEY_DOWN: 40,
KEY_DELETE: 46,
KEY_HOME: 36,
KEY_END: 35,
KEY_PAGEUP: 33,
KEY_PAGEDOWN: 34,
KEY_INSERT: 45,
cache: { },
relatedTarget: function(event) {
var element;
switch(event.type) {
case 'mouseover': element = event.fromElement; break;
case 'mouseout': element = event.toElement; break;
default: return null;
}
return Element.extend(element);
}
});
Event.Methods = (function() {
var isButton;
if (Prototype.Browser.IE) {
var buttonMap = { 0: 1, 1: 4, 2: 2 };
isButton = function(event, code) {
return event.button == buttonMap[code];
};
} else if (Prototype.Browser.WebKit) {
isButton = function(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 1 && event.metaKey;
default: return false;
}
};
} else {
isButton = function(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
};
}
return {
isLeftClick: function(event) { return isButton(event, 0) },
isMiddleClick: function(event) { return isButton(event, 1) },
isRightClick: function(event) { return isButton(event, 2) },
element: function(event) {
event = Event.extend(event);
var node = event.target,
type = event.type,
currentTarget = event.currentTarget;
if (currentTarget && currentTarget.tagName) {
// Firefox screws up the "click" event when moving between radio buttons
// via arrow keys. It also screws up the "load" and "error" events on images,
// reporting the document as the target instead of the original image.
if (type === 'load' || type === 'error' ||
(type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
&& currentTarget.type === 'radio'))
node = currentTarget;
}
if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
return Element.extend(node);
},
findElement: function(event, expression) {
var element = Event.element(event);
if (!expression) return element;
var elements = [element].concat(element.ancestors());
return Selector.findElement(elements, expression, 0);
},
pointer: function(event) {
var docElement = document.documentElement,
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))
};
},
pointerX: function(event) { return Event.pointer(event).x },
pointerY: function(event) { return Event.pointer(event).y },
stop: function(event) {
Event.extend(event);
event.preventDefault();
event.stopPropagation();
event.stopped = true;
}
};
})();
Event.extend = (function() {
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
m[name] = Event.Methods[name].methodize();
return m;
});
if (Prototype.Browser.IE) {
Object.extend(methods, {
stopPropagation: function() { this.cancelBubble = true },
preventDefault: function() { this.returnValue = false },
inspect: function() { return "[object Event]" }
});
return function(event) {
if (!event) return false;
if (event._extendedByPrototype) return event;
event._extendedByPrototype = Prototype.emptyFunction;
var pointer = Event.pointer(event);
Object.extend(event, {
target: event.srcElement,
relatedTarget: Event.relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
return Object.extend(event, methods);
};
} else {
Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
Object.extend(Event.prototype, methods);
return Prototype.K;
}
})();
Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
if (eventName && eventName.include(':')) return "dataavailable";
return eventName;
}
function getCacheForID(id) {
return cache[id] = cache[id] || { };
}
function getWrappersForEventName(id, eventName) {
var c = getCacheForID(id);
return c[eventName] = c[eventName] || [];
}
function createWrapper(element, eventName, handler) {
var id = getEventID(element);
var c = getWrappersForEventName(id, eventName);
if (c.pluck("handler").include(handler)) return false;
var wrapper = function(event) {
if (!Event || !Event.extend ||
(event.eventName && event.eventName != eventName))
return false;
Event.extend(event);
handler.call(element, event);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
function findWrapper(id, eventName, handler) {
var c = getWrappersForEventName(id, eventName);
return c.find(function(wrapper) { return wrapper.handler == handler });
}
function destroyWrapper(id, eventName, handler) {
var c = getCacheForID(id);
if (!c[eventName]) return false;
c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
}
function destroyCache() {
for (var id in cache)
for (var eventName in cache[id])
cache[id][eventName] = null;
}
// Internet Explorer needs to remove event handlers on page unload
// in order to avoid memory leaks.
if (window.attachEvent) {
window.attachEvent("onunload", destroyCache);
}
// Safari has a dummy event handler on page unload so that it won't
// use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
// object when page is returned to via the back button using its bfcache.
if (Prototype.Browser.WebKit) {
window.addEventListener('unload', Prototype.emptyFunction, false);
}
return {
observe: function(element, eventName, handler) {
element = $(element);
var name = getDOMEventName(eventName);
var wrapper = createWrapper(element, eventName, handler);
if (!wrapper) return element;
if (element.addEventListener) {
element.addEventListener(name, wrapper, false);
} else {
element.attachEvent("on" + name, wrapper);
}
return element;
},
stopObserving: function(element, eventName, handler) {
element = $(element);
var id = getEventID(element), name = getDOMEventName(eventName);
if (!handler && eventName) {
getWrappersForEventName(id, eventName).each(function(wrapper) {
element.stopObserving(eventName, wrapper.handler);
});
return element;
} else if (!eventName) {
Object.keys(getCacheForID(id)).each(function(eventName) {
element.stopObserving(eventName);
});
return element;
}
var wrapper = findWrapper(id, eventName, handler);
if (!wrapper) return element;
if (element.removeEventListener) {
element.removeEventListener(name, wrapper, false);
} else {
element.detachEvent("on" + name, wrapper);
}
destroyWrapper(id, eventName, handler);
return element;
},
fire: function(element, eventName, memo) {
element = $(element);
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {
element.dispatchEvent(event);
} else {
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
}
};
})());
Object.extend(Event, Event.Methods);
Element.addMethods({
fire: Event.fire,
observe: Event.observe,
stopObserving: Event.stopObserving
});
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
stopObserving: Element.Methods.stopObserving.methodize(),
loaded: false
});
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
var timer;
function fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.fire("dom:loaded");
document.loaded = true;
}
if (document.addEventListener) {
if (Prototype.Browser.WebKit) {
timer = window.setInterval(function() {
if (/loaded|complete/.test(document.readyState))
fireContentLoadedEvent();
}, 0);
Event.observe(window, "load", fireContentLoadedEvent);
} else {
document.addEventListener("DOMContentLoaded",
fireContentLoadedEvent, false);
}
} else {
document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
$("__onDOMContentLoaded").onreadystatechange = function() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
fireContentLoadedEvent();
}
};
}
})();

View File

@ -1,339 +0,0 @@
var Form = {
reset: function(form) {
$(form).reset();
return form;
},
serializeElements: function(elements, options) {
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)))) {
if (key in result) {
// a key is already present; construct an array of values
if (!Object.isArray(result[key])) result[key] = [result[key]];
result[key].push(value);
}
else result[key] = value;
}
}
return result;
});
return options.hash ? data : Object.toQueryString(data);
}
};
Form.Methods = {
serialize: function(form, options) {
return Form.serializeElements(Form.getElements(form), options);
},
getElements: function(form) {
return $A($(form).getElementsByTagName('*')).inject([],
function(elements, child) {
if (Form.Element.Serializers[child.tagName.toLowerCase()])
elements.push(Element.extend(child));
return elements;
}
);
},
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))
continue;
matchingInputs.push(Element.extend(input));
}
return matchingInputs;
},
disable: function(form) {
form = $(form);
Form.getElements(form).invoke('disable');
return form;
},
enable: function(form) {
form = $(form);
Form.getElements(form).invoke('enable');
return form;
},
findFirstElement: function(form) {
var elements = $(form).getElements().findAll(function(element) {
return 'hidden' != element.type && !element.disabled;
});
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'].include(element.tagName.toLowerCase());
});
},
focusFirstElement: function(form) {
form = $(form);
form.findFirstElement().activate();
return form;
},
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 = {
focus: function(element) {
$(element).focus();
return element;
},
select: function(element) {
$(element).select();
return element;
}
};
Form.Element.Methods = {
serialize: function(element) {
element = $(element);
if (!element.disabled && element.name) {
var value = element.getValue();
if (value != undefined) {
var pair = { };
pair[element.name] = value;
return Object.toQueryString(pair);
}
}
return '';
},
getValue: function(element) {
element = $(element);
var method = element.tagName.toLowerCase();
return Form.Element.Serializers[method](element);
},
setValue: function(element, value) {
element = $(element);
var method = element.tagName.toLowerCase();
Form.Element.Serializers[method](element, value);
return element;
},
clear: function(element) {
$(element).value = '';
return element;
},
present: function(element) {
return $(element).value != '';
},
activate: function(element) {
element = $(element);
try {
element.focus();
if (element.select && (element.tagName.toLowerCase() != 'input' ||
!['button', 'reset', 'submit'].include(element.type)))
element.select();
} catch (e) { }
return element;
},
disable: function(element) {
element = $(element);
element.disabled = true;
return element;
},
enable: function(element) {
element = $(element);
element.disabled = false;
return element;
}
};
/*--------------------------------------------------------------------------*/
var Field = Form.Element;
var $F = Form.Element.Methods.getValue;
/*--------------------------------------------------------------------------*/
Form.Element.Serializers = {
input: function(element, value) {
switch (element.type.toLowerCase()) {
case 'checkbox':
case 'radio':
return Form.Element.Serializers.inputSelector(element, value);
default:
return Form.Element.Serializers.textarea(element, value);
}
},
inputSelector: function(element, value) {
if (Object.isUndefined(value)) return element.checked ? element.value : null;
else element.checked = !!value;
},
textarea: function(element, value) {
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' ?
'selectOne' : 'selectMany'](element);
else {
var opt, currentValue, single = !Object.isArray(value);
for (var i = 0, length = element.length; i < length; i++) {
opt = element.options[i];
currentValue = this.optionValue(opt);
if (single) {
if (currentValue == value) {
opt.selected = true;
return;
}
}
else opt.selected = value.include(currentValue);
}
}
},
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;
}
};
/*--------------------------------------------------------------------------*/
Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
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) ?
this.lastValue != value : String(this.lastValue) != String(value)) {
this.callback(this.element, value);
this.lastValue = value;
}
}
});
Form.Element.Observer = Class.create(Abstract.TimedObserver, {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.Observer = Class.create(Abstract.TimedObserver, {
getValue: function() {
return Form.serialize(this.element);
}
});
/*--------------------------------------------------------------------------*/
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) {
this.callback(this.element, value);
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 'radio':
Event.observe(element, 'click', this.onElementEvent.bind(this));
break;
default:
Event.observe(element, 'change', this.onElementEvent.bind(this));
break;
}
}
}
});
Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.EventObserver = Class.create(Abstract.EventObserver, {
getValue: function() {
return Form.serialize(this.element);
}
});

View File

@ -1,101 +0,0 @@
function $H(object) {
return new Hash(object);
};
var Hash = Class.create(Enumerable, (function() {
function toQueryPair(key, value) {
if (Object.isUndefined(value)) return key;
return key + '=' + encodeURIComponent(String.interpret(value));
}
return {
initialize: function(object) {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
},
_each: function(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
pair.key = key;
pair.value = value;
iterator(pair);
}
},
set: function(key, value) {
return this._object[key] = value;
},
get: function(key) {
// simulating poorly supported hasOwnProperty
if (this._object[key] !== Object.prototype[key])
return this._object[key];
},
unset: function(key) {
var value = this._object[key];
delete this._object[key];
return value;
},
toObject: function() {
return Object.clone(this._object);
},
keys: function() {
return this.pluck('key');
},
values: function() {
return this.pluck('value');
},
index: function(value) {
var match = this.detect(function(pair) {
return pair.value === value;
});
return match && match.key;
},
merge: function(object) {
return this.clone().update(object);
},
update: function(object) {
return new Hash(object).inject(this, function(result, pair) {
result.set(pair.key, pair.value);
return result;
});
},
toQueryString: function() {
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)));
} else results.push(toQueryPair(key, values));
return results;
}).join('&');
},
inspect: function() {
return '#<Hash:{' + this.map(function(pair) {
return pair.map(Object.inspect).join(': ');
}).join(', ') + '}>';
},
toJSON: function() {
return Object.toJSON(this.toObject());
},
clone: function() {
return new Hash(this);
}
}
})());
Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;

48
src/lang.js Normal file
View File

@ -0,0 +1,48 @@
/**
* == 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"

449
src/lang/array.js Normal file
View File

@ -0,0 +1,449 @@
/** 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();
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)`.
**/
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))
return array.concat(value.flatten());
array.push(value);
return array;
});
}
/**
* 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) {
return !values.include(value);
});
}
/**
* 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();
}
/**
* 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)))
array.push(value);
return array;
});
}
/**
* 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) {
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) {
var value = Object.toJSON(object);
if (!Object.isUndefined(value)) results.push(value);
});
return '[' + results.join(', ') + ']';
}
/**
* Array#indexOf(item[, offset = 0]) -> Number
* - item (?): A value that may or may not be in the array.
* - 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;
if (i < 0) i = length + i;
for (; i < length; i++)
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;
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);
}
}
return array;
}
Object.extend(arrayProto, Enumerable);
if (!arrayProto._reverse)
arrayProto._reverse = arrayProto.reverse;
Object.extend(arrayProto, {
_each: _each,
clear: clear,
first: first,
last: last,
compact: compact,
flatten: flatten,
without: without,
reverse: reverse,
uniq: uniq,
intersect: intersect,
clone: clone,
toArray: clone,
size: size,
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;
})();

186
src/lang/class.js Normal file
View File

@ -0,0 +1,186 @@
/* 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) {
subclass.prototype = parent.prototype;
klass.prototype = new subclass;
parent.subclasses.push(klass);
}
for (var i = 0, length = properties.length; i < length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
klass.prototype.initialize = Prototype.emptyFunction;
klass.prototype.constructor = 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");
}
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") {
var method = value;
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
})(property).wrap(method);
value.valueOf = method.valueOf.bind(method);
value.toString = method.toString.bind(method);
}
this.prototype[property] = value;
}
return this;
}
return {
create: create,
Methods: {
addMethods: addMethods
}
};
})();

29
src/lang/date.js Normal file
View File

@ -0,0 +1,29 @@
/** 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) + '-' +
this.getUTCDate().toPaddedString(2) + 'T' +
this.getUTCHours().toPaddedString(2) + ':' +
this.getUTCMinutes().toPaddedString(2) + ':' +
this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

821
src/lang/enumerable.js Normal file
View File

@ -0,0 +1,821 @@
/** 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 {
this._each(function(value) {
iterator.call(context, value, index++);
});
} catch (e) {
if (e != $break) throw e;
}
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;
while ((index += number) < array.length)
slices.push(array.slice(index, index+number));
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;
this.each(function(value, index) {
result = result && !!iterator.call(context, value, index);
if (!result) throw $break;
});
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;
this.each(function(value, index) {
if (result = !!iterator.call(context, value, index))
throw $break;
});
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 = [];
this.each(function(value, index) {
results.push(iterator.call(context, value, index));
});
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) {
if (iterator.call(context, value, index)) {
result = value;
throw $break;
}
});
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) {
if (iterator.call(context, value, index))
results.push(value);
});
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));
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;
var found = false;
this.each(function(value) {
if (value == object) {
found = true;
throw $break;
}
});
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) {
while(slice.length < number) slice.push(fillWith);
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;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value >= result)
result = value;
});
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;
this.each(function(value, index) {
value = iterator.call(context, value, index);
if (result == null || value < result)
result = value;
});
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 = [];
this.each(function(value, index) {
(iterator.call(context, value, index) ?
trues : falses).push(value);
});
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) {
results.push(value[property]);
});
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) {
if (!iterator.call(context, value, index))
results.push(value);
});
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 {
value: value,
criteria: iterator.call(context, value, index)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
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()))
iterator = args.pop();
var collections = [this].concat(args).map($A);
return this.map(function(value, index) {
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,
all: all,
every: all,
any: any,
some: any,
collect: collect,
map: collect,
detect: detect,
findAll: findAll,
select: findAll,
filter: findAll,
grep: grep,
include: include,
member: include,
inGroupsOf: inGroupsOf,
inject: inject,
invoke: invoke,
max: max,
min: min,
partition: partition,
pluck: pluck,
reject: reject,
sortBy: sortBy,
toArray: toArray,
entries: toArray,
zip: zip,
size: size,
inspect: inspect,
find: detect
};
})();

391
src/lang/function.js Normal file
View File

@ -0,0 +1,391 @@
/** 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, '')
.replace(/\s+/g, '').split(',');
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);
return function() {
var a = merge(args, arguments);
return __method.apply(context, a);
}
}
/** 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) {
var a = update([event || window.event], args);
return __method.apply(context, a);
}
}
/**
* 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);
return function() {
var a = merge(args, arguments);
return __method.apply(this, a);
}
}
/**
* 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) {
var __method = this, args = slice.call(arguments, 1);
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() {
var a = update([__method.bind(this)], arguments);
return wrapper.apply(this, a);
}
}
/**
* 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;
return this._methodized = function() {
var a = update([this], arguments);
return __method.apply(null, a);
};
}
return {
argumentNames: argumentNames,
bind: bind,
bindAsEventListener: bindAsEventListener,
curry: curry,
delay: delay,
defer: defer,
wrap: wrap,
methodize: methodize
}
})());

379
src/lang/hash.js Normal file
View File

@ -0,0 +1,379 @@
/** 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];
pair.key = key;
pair.value = value;
iterator(pair);
}
}
/**
* 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;
});
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);
return result;
});
}
// 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)));
} else results.push(toQueryPair(key, values));
return results;
}).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,
set: set,
get: get,
unset: unset,
toObject: toObject,
toTemplateReplacements: toObject,
keys: keys,
values: values,
index: index,
merge: merge,
update: update,
toQueryString: toQueryString,
inspect: inspect,
toJSON: toJSON,
clone: clone
};
})());
Hash.from = $H;

168
src/lang/number.js Normal file
View File

@ -0,0 +1,168 @@
/** 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,
times: times,
toPaddedString: toPaddedString,
toJSON: toJSON,
abs: abs,
round: round,
ceil: ceil,
floor: floor
};
})());

296
src/lang/object.js Normal file
View File

@ -0,0 +1,296 @@
/** 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';
if (object === null) return 'null';
return object.inspect ? object.inspect() : String(object);
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
}
/**
* 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) {
case 'undefined':
case 'function':
case 'unknown': return;
case 'boolean': return object.toString();
}
if (object === null) return 'null';
if (object.toJSON) return object.toJSON();
if (isElement(object)) return;
var results = [];
for (var property in object) {
var value = toJSON(object[property]);
if (!isUndefined(value))
results.push(property.toJSON() + ': ' + value);
}
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 = [];
for (var property in object)
results.push(property);
return results;
}
/**
* 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 = [];
for (var property in object)
results.push(object[property]);
return results;
}
/**
* 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;
}
/**
* 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]";
}
/**
* 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]";
}
/**
* 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";
}
extend(Object, {
extend: extend,
inspect: inspect,
toJSON: toJSON,
toQueryString: toQueryString,
toHTML: toHTML,
keys: keys,
values: values,
clone: clone,
isElement: isElement,
isArray: isArray,
isHash: isHash,
isFunction: isFunction,
isString: isString,
isNumber: isNumber,
isUndefined: isUndefined
});
})();

View File

@ -0,0 +1,67 @@
/** 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;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
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);
this.timer = null;
},
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();
this.currentlyExecuting = false;
} catch(e) {
this.currentlyExecuting = false;
throw e;
}
}
}
});

76
src/lang/range.js Normal file
View File

@ -0,0 +1,76 @@
/** 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)) {
iterator(value);
value = value.succ();
}
}
/**
* ObjectRange#include(value) -> Boolean
*
* Determines whether the value is included in the range.
**/
function include(value) {
if (value < this.start)
return false;
if (this.exclusive)
return value < this.end;
return value <= this.end;
}
return {
initialize: initialize,
_each: _each,
include: include
};
})());

25
src/lang/regexp.js Normal file
View File

@ -0,0 +1,25 @@
/** 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');
};

525
src/lang/string.js Normal file
View File

@ -0,0 +1,525 @@
/** 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);
},
specialChar: {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'\\': '\\\\'
}
});
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);
result += String.interpret(replacement(match));
source = source.slice(match.index + match[0].length);
} else {
result += source, source = '';
}
}
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;
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
}
/** 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 ?
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, '');
}
/**
* 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');
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;');
}
/** 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,'&');
}
/**
* 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];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[key] = value;
}
return hash;
});
}
/**
* 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() : '';
});
}
/**
* 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();
}
/**
* String#dasherize() -> String
*
* Replaces every instance of the underscore character ("_") by a dash ("-").
**/
function dasherize() {
return this.replace(/_/g, '-');
}
/** 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);
});
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');
}
/**
* 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;
str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
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 {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
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;
}
/**
* 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;
}
/**
* 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,
stripTags: stripTags,
stripScripts: stripScripts,
extractScripts: extractScripts,
evalScripts: evalScripts,
escapeHTML: escapeHTML,
unescapeHTML: unescapeHTML,
toQueryParams: toQueryParams,
parseQuery: toQueryParams,
toArray: toArray,
succ: succ,
times: times,
camelize: camelize,
capitalize: capitalize,
underscore: underscore,
dasherize: dasherize,
inspect: inspect,
toJSON: toJSON,
unfilterJSON: unfilterJSON,
isJSON: isJSON,
evalJSON: evalJSON,
include: include,
startsWith: startsWith,
endsWith: endsWith,
empty: empty,
blank: blank,
interpolate: interpolate
};
})());

154
src/lang/template.js Normal file
View File

@ -0,0 +1,154 @@
/** 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))
object = object.toTemplateReplacements();
return this.template.gsub(this.pattern, function(match) {
if (object == null) return (match[1] + '');
var before = match[1] || '';
if (before == '\\') return match[2];
var ctx = object, expr = match[3],
pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
match = pattern.exec(expr);
if (match == null) return before;
while (match != null) {
var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
ctx = ctx[comp];
if (null == ctx || '' == match[3]) break;
expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
match = pattern.exec(expr);
}
return before + String.interpret(ctx);
});
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

View File

@ -1,27 +0,0 @@
Object.extend(Number.prototype, {
toColorPart: function() {
return this.toPaddedString(2, 16);
},
succ: function() {
return this + 1;
},
times: function(iterator, context) {
$R(0, this, true).each(iterator, context);
return this;
},
toPaddedString: function(length, radix) {
var string = this.toString(radix || 10);
return '0'.times(length - string.length) + string;
},
toJSON: function() {
return isFinite(this) ? this.toString() : 'null';
}
});
$w('abs round ceil floor').each(function(method){
Number.prototype[method] = Math[method].methodize();
});

73
src/prototype.js vendored
View File

@ -1,43 +1,66 @@
<%= include 'HEADER' %>
/* 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/
*
*--------------------------------------------------------------------------*/
var Prototype = {
Version: '<%= PROTOTYPE_VERSION %>',
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/)
},
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)
}
})(),
BrowserFeatures: {
XPath: !!document.evaluate,
SelectorsAPI: !!document.querySelector,
ElementExtensions: !!window.HTMLElement,
SpecificElementExtensions:
document.createElement('div')['__proto__'] &&
document.createElement('div')['__proto__'] !==
document.createElement('form')['__proto__']
ElementExtensions: (function() {
var constructor = window.Element || window.HTMLElement;
return !!(constructor && constructor.prototype);
})(),
SpecificElementExtensions: (function() {
// First, try the named class
if (typeof window.HTMLDivElement !== 'undefined')
return true;
var div = document.createElement('div'),
form = document.createElement('form'),
isSupported = false;
if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
isSupported = true;
}
div = form = null;
return isSupported;
})()
},
ScriptFragment: '<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;
<%= include 'base.js', 'string.js' %>
//= require "lang"
//= require "ajax"
//= require "dom"
<%= include 'enumerable.js', 'array.js', 'number.js', 'hash.js', 'range.js' %>
<%= include 'ajax.js', 'dom.js', 'selector.js', 'form.js', 'event.js', 'deprecated.js' %>
Element.addMethods();
//= require "deprecated"

View File

@ -1,27 +0,0 @@
var ObjectRange = Class.create(Enumerable, {
initialize: function(start, end, exclusive) {
this.start = start;
this.end = end;
this.exclusive = exclusive;
},
_each: function(iterator) {
var value = this.start;
while (this.include(value)) {
iterator(value);
value = value.succ();
}
},
include: function(value) {
if (value < this.start)
return false;
if (this.exclusive)
return value < this.end;
return value <= this.end;
}
});
var $R = function(start, end, exclusive) {
return new ObjectRange(start, end, exclusive);
};

29
src/selector_engine.js Normal file
View File

@ -0,0 +1,29 @@
Prototype._original_property = window.Sizzle;
//= require "sizzle"
Prototype.Selector = (function(engine) {
function extend(elements) {
for (var i = 0, length = elements.length; i < length; i++) {
Element.extend(elements[i]);
}
return elements;
}
function select(selector, scope) {
return extend(engine(selector, scope || document));
}
function match(element, selector) {
return engine.matches(selector, [element]).length == 1;
}
return {
engine: engine,
select: select,
match: match
};
})(Sizzle);
// Restore globals.
window.Sizzle = Prototype._original_property;
delete Prototype._original_property;

1015
src/sizzle.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,266 +0,0 @@
Object.extend(String, {
interpret: function(value) {
return value == null ? '' : String(value);
},
specialChar: {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'\\': '\\\\'
}
});
Object.extend(String.prototype, {
gsub: function(pattern, replacement) {
var result = '', source = this, match;
replacement = arguments.callee.prepareReplacement(replacement);
while (source.length > 0) {
if (match = source.match(pattern)) {
result += source.slice(0, match.index);
result += String.interpret(replacement(match));
source = source.slice(match.index + match[0].length);
} else {
result += source, source = '';
}
}
return result;
},
sub: function(pattern, replacement, count) {
replacement = this.gsub.prepareReplacement(replacement);
count = Object.isUndefined(count) ? 1 : count;
return this.gsub(pattern, function(match) {
if (--count < 0) return match[0];
return replacement(match);
});
},
scan: function(pattern, iterator) {
this.gsub(pattern, iterator);
return String(this);
},
truncate: function(length, truncation) {
length = length || 30;
truncation = Object.isUndefined(truncation) ? '...' : truncation;
return this.length > length ?
this.slice(0, length - truncation.length) + truncation : String(this);
},
strip: function() {
return this.replace(/^\s+/, '').replace(/\s+$/, '');
},
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
stripScripts: function() {
return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
},
extractScripts: function() {
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];
});
},
evalScripts: function() {
return this.extractScripts().map(function(script) { return eval(script) });
},
escapeHTML: function() {
var self = arguments.callee;
self.text.data = this;
return self.div.innerHTML;
},
unescapeHTML: function() {
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) : '';
},
toQueryParams: function(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());
var value = pair.length > 1 ? pair.join('=') : pair[0];
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[key] = value;
}
return hash;
});
},
toArray: function() {
return this.split('');
},
succ: function() {
return this.slice(0, this.length - 1) +
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
},
times: function(count) {
return count < 1 ? '' : new Array(count + 1).join(this);
},
camelize: function() {
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;
},
capitalize: function() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
},
underscore: function() {
return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
},
dasherize: function() {
return this.gsub(/_/,'-');
},
inspect: function(useDoubleQuotes) {
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
var character = String.specialChar[match[0]];
return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
});
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
},
toJSON: function() {
return this.inspect(true);
},
unfilterJSON: function(filter) {
return this.sub(filter || Prototype.JSONFilter, '#{1}');
},
isJSON: function() {
var str = this;
if (str.blank()) return false;
str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
},
evalJSON: function(sanitize) {
var json = this.unfilterJSON();
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
},
include: function(pattern) {
return this.indexOf(pattern) > -1;
},
startsWith: function(pattern) {
return this.indexOf(pattern) === 0;
},
endsWith: function(pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
},
empty: function() {
return this == '';
},
blank: function() {
return /^\s*$/.test(this);
},
interpolate: function(object, pattern) {
return new Template(this, pattern).evaluate(object);
}
});
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,'>');
}
});
String.prototype.gsub.prepareReplacement = function(replacement) {
if (Object.isFunction(replacement)) return replacement;
var template = new Template(replacement);
return function(match) { return template.evaluate(match) };
};
String.prototype.parseQuery = String.prototype.toQueryParams;
Object.extend(String.prototype.escapeHTML, {
div: document.createElement('div'),
text: document.createTextNode('')
});
String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
var Template = Class.create({
initialize: function(template, pattern) {
this.template = template.toString();
this.pattern = pattern || Template.Pattern;
},
evaluate: function(object) {
if (Object.isFunction(object.toTemplateReplacements))
object = object.toTemplateReplacements();
return this.template.gsub(this.pattern, function(match) {
if (object == null) return '';
var before = match[1] || '';
if (before == '\\') return match[2];
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].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);
});
}
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

View File

@ -225,14 +225,38 @@
})
</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(){ alert('observed unload fired!') })
Event.observe(window, 'unload', function(event){
if (!event.target) {
alert('FAILURE: event.target should not be null!');
}
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

View File

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

View File

@ -1,562 +0,0 @@
// 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;
}
});

View File

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

@ -1,35 +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 %></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

@ -1,42 +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>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 = function() {};
request.options.onComplete = Prototype.emptyFunction;
this.assertIdentical(Prototype.emptyFunction, options.onComplete);
},
@ -105,7 +105,7 @@ new Test.Unit.Runner({
this.assertEqual(1, Ajax.Responders.responders.length);
var dummyResponder = {
onComplete: function(req) { /* dummy */ }
onComplete: Prototype.emptyFunction
};
Ajax.Responders.register(dummyResponder);
@ -376,4 +376,4 @@ new Test.Unit.Runner({
this.info(message);
}
}
});
});

View File

@ -33,6 +33,16 @@ 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());
@ -137,14 +147,6 @@ 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());
@ -185,5 +187,10 @@ 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,354 +1,4 @@
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;
@ -375,7 +25,7 @@ new Test.Unit.Runner({
this.assert(Prototype.Browser.WebKit);
}
if(!!window.opera) {
if(Object.prototype.toString.call(window.opera) === '[object Opera]') {
this.info('Running on Opera');
this.assert(Prototype.Browser.Opera);
}
@ -389,134 +39,5 @@ 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

@ -0,0 +1,53 @@
{
"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
}
]
}

136
test/unit/class_test.js Normal file
View File

@ -0,0 +1,136 @@
new Test.Unit.Runner({
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 Bar = Class.create(Foo, {
valueOf: function() { return "myValueOf" }
});
var Parent = Class.create({
m1: function(){ return 'm1' },
m2: function(){ return 'm2' }
});
var Child = Class.create(Parent, {
m1: function($super) { return 'm1 child' },
m2: function($super) { return 'm2 child' }
});
this.assert(new Child().m1.toString().indexOf('m1 child') > -1);
this.assertEqual("toString", new Foo().toString());
this.assertEqual("valueOf", new Foo().valueOf());
this.assertEqual("toString", new Bar().toString());
this.assertEqual("myValueOf", new Bar().valueOf());
}
});

5
test/unit/date_test.js Normal file
View File

@ -0,0 +1,5 @@
new Test.Unit.Runner({
testDateToJSON: function() {
this.assertEqual('\"1970-01-01T00:00:00Z\"', new Date(Date.UTC(1970, 0, 1)).toJSON());
}
});

View File

@ -73,7 +73,15 @@ new Test.Unit.Runner({
this.assertElementsMatch(document.getElementsByClassName('A'), 'p.A', 'ul#class_names_ul.A', 'li.A.C');
if (Prototype.Browser.IE)
var isElementPrototypeSupported = (function(){
var el = document.createElement('div');
var result = typeof el.show != 'undefined';
el = null;
return result;
})();
if (!isElementPrototypeSupported)
this.assertUndefined(document.getElementById('unextended').show);
this.assertElementsMatch(div.getElementsByClassName('B'), 'ul#class_names_ul.A.B', 'div.B.C.D');
@ -95,7 +103,8 @@ new Test.Unit.Runner({
this.assertElementsMatch(list.getElementsByClassName({}));
// those lookups shouldn't have extended all nodes in document
if (Prototype.Browser.IE) this.assertUndefined(document.getElementById('unextended')['show']);
if (!isElementPrototypeSupported)
this.assertUndefined(document.getElementById('unextended')['show']);
},
testElementInsertWithHTML: function() {
@ -369,6 +378,15 @@ 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);
@ -623,6 +641,20 @@ 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);
@ -647,6 +679,9 @@ 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() {
@ -805,7 +840,29 @@ new Test.Unit.Runner({
this.assert(
$('style_test_3').setOpacity(0.9999999).getStyle('opacity') > 0.999
);
if (Prototype.Browser.IE) {
/*
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) {
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());
@ -894,6 +951,9 @@ new Test.Unit.Runner({
// height/width could always be calculated if it's set to "auto" (Firefox)
this.assertNotNull($('auto_dimensions').getStyle('height'));
this.assertNotNull($('auto_dimensions').getStyle('width'));
// assert that using getStyle on an object without a style property returns null
this.assertNull(Element.Methods.getStyle({}, 'width'));
},
testElementGetOpacity: function() {
@ -907,7 +967,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').cloneNode(true);
var clone = $('cloned_element_attributes_issue').clone(true);
clone.writeAttribute('foo', 'cloned');
this.assertEqual('cloned', clone.readAttribute('foo'));
this.assertEqual('original', $('cloned_element_attributes_issue').readAttribute('foo'));
@ -1064,13 +1124,20 @@ 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 (Prototype.Browser.IE)
this.assertMatch(/name=["']?my_input_field["']?/, $('my_input_field').outerHTML);
if ('outerHTML' in document.documentElement) {
this.assertMatch(/name=["']?my_input_field["']?/, $('my_input_field_id').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() {
@ -1258,6 +1325,10 @@ new Test.Unit.Runner({
this.assertEnumEqual([0,0], offset);
this.assertIdentical(0, offset.top);
this.assertIdentical(0, offset.left);
var innerEl = new Element('div'), outerEl = new Element('div');
outerEl.appendChild(innerEl);
this.assertEnumEqual([0,0], innerEl.cumulativeOffset());
},
testViewportOffset: function() {
@ -1365,6 +1436,69 @@ 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);
}
});
@ -1380,4 +1514,4 @@ function preservingBrowserDimensions(callback) {
} finally {
window.resizeTo(original.width, original.height);
}
}
}

View File

@ -136,6 +136,14 @@ 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,6 +109,24 @@ 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++ };
@ -138,23 +156,27 @@ new Test.Unit.Runner({
},
testStopObservingRemovesHandlerFromCache: function() {
var span = $("span"), observer = function() { }, eventID;
var span = $("span"), observer = Prototype.emptyFunction, eventID;
span.observe("test:somethingHappened", observer);
eventID = span._prototypeEventID;
this.assert(Event.cache[eventID]);
this.assert(Object.isArray(Event.cache[eventID]["test:somethingHappened"]));
this.assertEqual(1, Event.cache[eventID]["test:somethingHappened"].length);
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);
span.stopObserving("test:somethingHappened", observer);
this.assert(Event.cache[eventID]);
this.assert(Object.isArray(Event.cache[eventID]["test:somethingHappened"]));
this.assertEqual(0, Event.cache[eventID]["test:somethingHappened"].length);
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);
},
testObserveAndStopObservingAreChainable: function() {
var span = $("span"), observer = function() { };
var span = $("span"), observer = Prototype.emptyFunction;
this.assertEqual(span, span.observe("test:somethingHappened", observer));
this.assertEqual(span, span.stopObserving("test:somethingHappened", observer));
@ -162,6 +184,8 @@ new Test.Unit.Runner({
span.observe("test:somethingHappened", observer);
this.assertEqual(span, span.stopObserving("test:somethingHappened"));
this.assertEqual(span, span.stopObserving("test:somethingOtherHappened", observer));
span.observe("test:somethingHappened", observer);
this.assertEqual(span, span.stopObserving());
this.assertEqual(span, span.stopObserving()); // assert it again, after there are no observers
@ -188,7 +212,7 @@ new Test.Unit.Runner({
testEventStopped: function() {
var span = $("span"), event;
span.observe("test:somethingHappened", function() { });
span.observe("test:somethingHappened", Prototype.emptyFunction);
event = span.fire("test:somethingHappened");
this.assert(!event.stopped, "event.stopped should be false with an empty observer");
span.stopObserving("test:somethingHappened");

View File

@ -1,26 +1,3 @@
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) {
@ -46,12 +23,12 @@ var Cat = Class.create(Animal, {
// empty subclass
var Mouse = Class.create(Animal, {});
//mixins
//mixins
var Sellable = {
getValue: function(pricePerKilo) {
return this.weight * pricePerKilo;
},
inspect: function() {
return '#<Sellable: #{weight}kg>'.interpolate(this);
}
@ -94,12 +71,12 @@ var Ox = Class.create(Animal, Sellable, Reproduceable, {
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

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

View File

@ -0,0 +1,7 @@
var Person = function(name){
this.name = name;
};
Person.prototype.toJSON = function() {
return '-' + this.name;
};

View File

@ -53,6 +53,17 @@
</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
@ -66,4 +77,12 @@
<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

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

View File

@ -26,6 +26,10 @@ 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(){
@ -287,8 +291,8 @@ new Test.Unit.Runner({
form = $('bigform');
var input = form['tf_text'], select = form['tf_selectOne'];
input._extendedByPrototype = select._extendedByPrototype = false;
input._extendedByPrototype = select._extendedByPrototype = void 0;
this.assert($(input).anInputMethod);
this.assert(!input.aSelectMethod);
this.assertEqual('input', input.anInputMethod());
@ -313,7 +317,7 @@ new Test.Unit.Runner({
// with empty action attribute
request = $("ffe").request({ method: 'post' });
this.assert(request.url.include("unit/tmp/form_test.html"),
this.assert(request.url.include("/tmp/form_test.html"),
'wrong default action for form element with empty action attribute');
},
@ -355,5 +359,24 @@ 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));
}
});

133
test/unit/function_test.js Normal file
View File

@ -0,0 +1,133 @@
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());
function named4(one,
two,
three) {}
this.assertEnumEqual($w('one two three'), named4.argumentNames());
function named5(/*foo*/ foo, /* bar */ bar, /*****/ baz) {}
this.assertEnumEqual($w("foo bar baz"), named5.argumentNames());
function named6(
/*foo*/ foo,
/**/bar,
/* baz */ /* baz */ baz,
// Skip a line just to screw with the regex...
/* thud */ thud) {}
this.assertEnumEqual($w("foo bar baz thud"), named6.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());
},
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);
}
}
});

180
test/unit/object_test.js Normal file
View File

@ -0,0 +1,180 @@
new Test.Unit.Runner({
testObjectExtend: function() {
var object = {foo: 'foo', bar: [1, 2, 3]};
this.assertIdentical(object, Object.extend(object));
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3]}, object);
this.assertIdentical(object, Object.extend(object, {bla: 123}));
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: 123}, object);
this.assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: null},
Object.extend(object, {bla: null}));
},
testObjectToQueryString: function() {
this.assertEqual('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'}));
},
testObjectClone: function() {
var object = {foo: 'foo', bar: [1, 2, 3]};
this.assertNotIdentical(object, Object.clone(object));
this.assertHashEqual(object, Object.clone(object));
this.assertHashEqual({}, Object.clone());
var clone = Object.clone(object);
delete clone.bar;
this.assertHashEqual({foo: 'foo'}, clone,
"Optimizing Object.clone perf using prototyping doesn't allow properties to be deleted.");
},
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(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() {
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);
}
});

View File

@ -0,0 +1,35 @@
new Test.Unit.Runner({
testPeriodicalExecuterStop: function() {
var peEventCount = 0;
function peEventFired(pe) {
if (++peEventCount > 2) pe.stop();
}
// peEventFired will stop the PeriodicalExecuter after 3 callbacks
new PeriodicalExecuter(peEventFired, 0.05);
this.wait(600, function() {
this.assertEqual(3, peEventCount);
});
},
testOnTimerEventMethod: function() {
var testcase = this,
pe = {
onTimerEvent: PeriodicalExecuter.prototype.onTimerEvent,
execute: function() {
testcase.assert(pe.currentlyExecuting);
}
};
pe.onTimerEvent();
this.assert(!pe.currentlyExecuting);
pe.execute = function() {
testcase.assert(pe.currentlyExecuting);
throw new Error()
}
this.assertRaise('Error', pe.onTimerEvent.bind(pe));
this.assert(!pe.currentlyExecuting);
}
});

43
test/unit/prototype_test.js vendored Normal file
View File

@ -0,0 +1,43 @@
new Test.Unit.Runner({
testBrowserDetection: function() {
var results = $H(Prototype.Browser).map(function(engine){
return engine;
}).partition(function(engine){
return engine[1] === true
});
var trues = results[0], falses = results[1];
this.info('User agent string is: ' + navigator.userAgent);
this.assert(trues.size() == 0 || trues.size() == 1,
'There should be only one or no browser detected.');
// we should have definite trues or falses here
trues.each(function(result) {
this.assert(result[1] === true);
}, this);
falses.each(function(result) {
this.assert(result[1] === false);
}, this);
if(navigator.userAgent.indexOf('AppleWebKit/') > -1) {
this.info('Running on WebKit');
this.assert(Prototype.Browser.WebKit);
}
if(!!window.opera) {
this.info('Running on Opera');
this.assert(Prototype.Browser.Opera);
}
if(!!(window.attachEvent && !window.opera)) {
this.info('Running on IE');
this.assert(Prototype.Browser.IE);
}
if(navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1) {
this.info('Running on Gecko');
this.assert(Prototype.Browser.Gecko);
}
}
});

42
test/unit/regexp_test.js Normal file
View File

@ -0,0 +1,42 @@
new Test.Unit.Runner({
testRegExpEscape: function() {
this.assertEqual('word', RegExp.escape('word'));
this.assertEqual('\\/slashes\\/', RegExp.escape('/slashes/'));
this.assertEqual('\\\\backslashes\\\\', RegExp.escape('\\backslashes\\'));
this.assertEqual('\\\\border of word', RegExp.escape('\\border of word'));
this.assertEqual('\\(\\?\\:non-capturing\\)', RegExp.escape('(?:non-capturing)'));
this.assertEqual('non-capturing', new RegExp(RegExp.escape('(?:') + '([^)]+)').exec('(?:non-capturing)')[1]);
this.assertEqual('\\(\\?\\=positive-lookahead\\)', RegExp.escape('(?=positive-lookahead)'));
this.assertEqual('positive-lookahead', new RegExp(RegExp.escape('(?=') + '([^)]+)').exec('(?=positive-lookahead)')[1]);
this.assertEqual('\\(\\?<\\=positive-lookbehind\\)', RegExp.escape('(?<=positive-lookbehind)'));
this.assertEqual('positive-lookbehind', new RegExp(RegExp.escape('(?<=') + '([^)]+)').exec('(?<=positive-lookbehind)')[1]);
this.assertEqual('\\(\\?\\!negative-lookahead\\)', RegExp.escape('(?!negative-lookahead)'));
this.assertEqual('negative-lookahead', new RegExp(RegExp.escape('(?!') + '([^)]+)').exec('(?!negative-lookahead)')[1]);
this.assertEqual('\\(\\?<\\!negative-lookbehind\\)', RegExp.escape('(?<!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')
);
}
});

View File

@ -0,0 +1,50 @@
/*
<div id="test_div_parent" class="test_class">
<div id="test_div_child" class="test_class">
</div>
</div>
*/
new Test.Unit.Runner({
testEngine: function() {
this.assert(Prototype.Selector.engine);
},
testSelect: function() {
var elements = Prototype.Selector.select('.test_class');
this.assert(Object.isArray(elements));
this.assertEqual(2, elements.length);
this.assertEqual('test_div_parent', elements[0].id);
this.assertEqual('test_div_child', elements[1].id);
},
testSelectWithContext: function() {
var elements = Prototype.Selector.select('.test_class', $('test_div_parent'));
this.assert(Object.isArray(elements));
this.assertEqual(1, elements.length);
this.assertEqual('test_div_child', elements[0].id);
},
testSelectWithEmptyResult: function() {
var elements = Prototype.Selector.select('.non_existent');
this.assert(Object.isArray(elements));
this.assertEqual(0, elements.length);
},
testMatch: function() {
var element = $('test_div_parent');
this.assertEqual(true, Prototype.Selector.match(element, '.test_class'));
this.assertEqual(false, Prototype.Selector.match(element, '.non_existent'));
},
testFind: function() {
var elements = document.getElementsByTagName('*'),
expression = '.test_class';
this.assertEqual('test_div_parent', Prototype.Selector.find(elements, expression).id);
this.assertEqual('test_div_child', Prototype.Selector.find(elements, expression, 1).id);
}
});

View File

@ -1,5 +1,9 @@
var $RunBenchmarks = false;
function reduce(arr) {
return arr.length > 1 ? arr : arr[0];
}
new Test.Unit.Runner({
testSelectorWithTagName: function() {
@ -87,6 +91,10 @@ 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"]'));
@ -189,12 +197,12 @@ new Test.Unit.Runner({
testSelectorWithAdjacence: function() {
this.assertEnumEqual([$('uncle')], $$('div.brothers + div.brothers'));
this.assertEnumEqual([$('uncle')], $$('div.brothers + div'));
this.assertEqual($('level2_2'), $$('#level2_1+span').reduce());
this.assertEqual($('level2_2'), $$('#level2_1 + span').reduce());
this.assertEqual($('level2_2'), $$('#level2_1 + *').reduce());
this.assertEqual($('level2_2'), reduce($$('#level2_1+span')));
this.assertEqual($('level2_2'), reduce($$('#level2_1 + span')));
this.assertEqual($('level2_2'), reduce($$('#level2_1 + *')));
this.assertEnumEqual([], $$('#level2_2 + span'));
this.assertEqual($('level3_2'), $$('#level3_1 + span').reduce());
this.assertEqual($('level3_2'), $$('#level3_1 + *').reduce());
this.assertEqual($('level3_2'), reduce($$('#level3_1 + span')));
this.assertEqual($('level3_2'), reduce($$('#level3_1 + *')));
this.assertEnumEqual([], $$('#level3_2 + *'));
this.assertEnumEqual([], $$('#level3_1 + em'));
$RunBenchmarks && this.wait(500, function() {
@ -204,8 +212,8 @@ new Test.Unit.Runner({
testSelectorWithLaterSibling: function() {
this.assertEnumEqual([$('list')], $$('h1 ~ ul'));
this.assertEqual($('level2_2'), $$('#level2_1 ~ span').reduce());
this.assertEnumEqual($('level2_2', 'level2_3'), $$('#level2_1 ~ *').reduce());
this.assertEqual($('level2_2'), reduce($$('#level2_1 ~ span')));
this.assertEnumEqual($('level2_2', 'level2_3'), reduce($$('#level2_1 ~ *')));
this.assertEnumEqual([], $$('#level2_2 ~ span'));
this.assertEnumEqual([], $$('#level3_2 ~ *'));
this.assertEnumEqual([], $$('#level3_1 ~ em'));
@ -348,14 +356,6 @@ new Test.Unit.Runner({
this.assert(typeof results[2].show == 'function');
},
testCountedIsNotAnAttribute: function() {
var el = $('list');
Selector.handlers.mark([el]);
this.assert(!el.innerHTML.include("_counted"));
Selector.handlers.unmark([el]);
this.assert(!el.innerHTML.include("_counted"));
},
testCopiedNodesGetIncluded: function() {
this.assertElementsMatch(
Selector.matchElements($('counted_container').descendants(), 'div'),
@ -367,11 +367,48 @@ 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,6 +44,11 @@ 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() {
@ -59,6 +64,19 @@ 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';
@ -190,6 +208,7 @@ 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() {
@ -250,6 +269,8 @@ 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);
},
@ -296,20 +317,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));
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', 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 = zero - one = one - two-zero - two-zero-name - two-zero - two-zero - \\ - first\\', new Template(source).evaluate(subject));
},
@ -321,6 +342,12 @@ 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

@ -0,0 +1,27 @@
<!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 Submodule

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

View File

@ -1,11 +1,11 @@
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
/* Portions of the Prototype.LegacySelector class are derived from Jack Slocum's DomQuery,
* part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
* license. Please see http://www.yui-ext.com/ for more information. */
var Selector = Class.create({
Prototype.LegacySelector = Class.create({
initialize: function(expression) {
this.expression = expression.strip();
if (this.shouldUseSelectorsAPI()) {
this.mode = 'selectorsAPI';
} else if (this.shouldUseXPath()) {
@ -15,102 +15,133 @@ var Selector = Class.create({
this.mode = "normal";
this.compileMatcher();
}
},
shouldUseXPath: 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;
return true;
},
shouldUseXPath: (function() {
// Some versions of Opera 9.x produce incorrect results when using XPath
// with descendant combinators.
// see: http://opera.remcol.ath.cx/bugs/index.php?action=bug&id=652
var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
var isBuggy = false;
if (document.evaluate && window.XPathResult) {
var el = document.createElement('div');
el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
"//*[local-name()='li' or local-name()='LI']";
var result = document.evaluate(xpath, el, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
isBuggy = (result.snapshotLength !== 2);
el = null;
}
return isBuggy;
})();
return function() {
if (!Prototype.BrowserFeatures.XPath) return false;
var e = this.expression;
// Safari 3 chokes on :*-of-type and :empty
if (Prototype.Browser.WebKit &&
(e.include("-of-type") || e.include(":empty")))
return false;
// XPath can't do namespaced attributes, nor can it read
// the "checked" property from DOM nodes
if ((/(\[[\w-]*?:|:checked)/).test(e))
return false;
if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
return true;
}
})(),
shouldUseSelectorsAPI: function() {
if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
if (!Selector._div) Selector._div = new Element('div');
// Make sure the browser treats the selector as valid. Test on an
// isolated element to minimize cost of this check.
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 {
Selector._div.querySelector(this.expression);
Prototype.LegacySelector._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;
if (Selector._cache[e]) {
this.matcher = Selector._cache[e];
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 = Selector.handlers, c = false, n;"];
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 in ps) {
p = ps[i];
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[i]) ? c[i](m) :
new Template(c[i]).evaluate(m));
this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
new Template(c[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.matcher.push("return h.unique(n);\n}");
eval(this.matcher.join('\n'));
Selector._cache[this.expression] = this.matcher;
Prototype.LegacySelector._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;
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 in ps) {
if (m = e.match(ps[i])) {
this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
new Template(x[i]).evaluate(m));
for (var i = 0; i<len; i++) {
name = ps[i].name;
if (m = e.match(ps[i].re)) {
this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
new Template(x[name]).evaluate(m));
e = e.replace(m[0], '');
break;
}
}
}
this.xpath = this.matcher.join('');
Selector._cache[this.expression] = this.xpath;
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
@ -118,6 +149,8 @@ var Selector = Class.create({
// 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;
}
@ -131,22 +164,23 @@ var Selector = Class.create({
return this.matcher(root);
}
},
match: function(element) {
this.tokens = [];
var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
var le, p, m;
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 in ps) {
p = ps[i];
for (var i = 0; i<len; i++) {
p = ps[i].re;
name = ps[i].name;
if (m = e.match(p)) {
// use the Selector.assertions methods unless the selector
// use the Prototype.LegacySelector.assertions methods unless the selector
// is too complex.
if (as[i]) {
this.tokens.push([i, Object.clone(m)]);
if (as[name]) {
this.tokens.push([name, Object.clone(m)]);
e = e.replace(m[0], '');
} else {
// reluctantly do a document-wide search
@ -156,38 +190,56 @@ var Selector = Class.create({
}
}
}
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)) {
if (!Prototype.LegacySelector.assertions[name](element, matches)) {
match = false; break;
}
}
return match;
},
toString: function() {
return this.expression;
},
inspect: function() {
return "#<Selector:" + this.expression.inspect() + ">";
return "#<Prototype.LegacySelector:" + this.expression.inspect() + ">";
}
});
Object.extend(Selector, {
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) {
tagName: function(m) {
if (m[1] == '*') return '';
return "[local-name()='" + m[1].toLowerCase() +
return "[local-name()='" + m[1].toLowerCase() +
"' or local-name()='" + m[1].toUpperCase() + "']";
},
className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
@ -199,13 +251,13 @@ Object.extend(Selector, {
attr: function(m) {
m[1] = m[1].toLowerCase();
m[3] = m[5] || m[6];
return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
return new Template(Prototype.LegacySelector.xpath.operators[m[2]]).evaluate(m);
},
pseudo: function(m) {
var h = Selector.xpath.pseudos[m[1]];
var h = Prototype.LegacySelector.xpath.pseudos[m[1]];
if (!h) return '';
if (Object.isFunction(h)) return h(m);
return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
return new Template(Prototype.LegacySelector.xpath.pseudos[m[1]]).evaluate(m);
},
operators: {
'=': "[@#{1}='#{3}']",
@ -225,43 +277,44 @@ Object.extend(Selector, {
'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 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 in p) {
if (m = e.match(p[i])) {
v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
for (var i = 0; i<len; i++) {
name = p[i].name;
if (m = e.match(p[i].re)) {
v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
exclusion.push("(" + v.substring(1, v.length - 1) + ")");
e = e.replace(m[0], '');
break;
}
}
}
}
return "[not(" + exclusion.join(" and ") + ")]";
},
'nth-child': function(m) {
return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
'nth-child': function(m) {
return Prototype.LegacySelector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
},
'nth-last-child': function(m) {
return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
return Prototype.LegacySelector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
},
'nth-of-type': function(m) {
return Selector.xpath.pseudos.nth("position() ", m);
return Prototype.LegacySelector.xpath.pseudos.nth("position() ", m);
},
'nth-last-of-type': function(m) {
return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
return Prototype.LegacySelector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
},
'first-of-type': function(m) {
m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](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 Selector.xpath.pseudos['nth-last-of-type'](m);
m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-last-of-type'](m);
},
'only-of-type': function(m) {
var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
var p = Prototype.LegacySelector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
},
nth: function(fragment, m) {
var mm, formula = m[6], predicate;
@ -281,9 +334,9 @@ Object.extend(Selector, {
}
}
},
criteria: {
tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
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;',
@ -293,7 +346,7 @@ Object.extend(Selector, {
},
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);
return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
},
descendant: 'c = "descendant";',
child: 'c = "child";',
@ -301,25 +354,24 @@ Object.extend(Selector, {
laterSibling: 'c = "laterSibling";'
},
patterns: {
patterns: [
// combinators must be listed first
// (and descendant needs to be last combinator)
laterSibling: /^\s*~\s*/,
child: /^\s*>\s*/,
adjacent: /^\s*\+\s*/,
descendant: /^\s/,
{ name: 'laterSibling', re: /^\s*~\s*/ },
{ name: 'child', re: /^\s*>\s*/ },
{ name: 'adjacent', re: /^\s*\+\s*/ },
{ name: 'descendant', re: /^\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
{ 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();
@ -339,10 +391,10 @@ Object.extend(Selector, {
attr: function(element, matches) {
var nodeValue = Element.readAttribute(element, matches[1]);
return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
return nodeValue && Prototype.LegacySelector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
}
},
handlers: {
// UTILITY FUNCTIONS
// joins two collections
@ -351,7 +403,7 @@ Object.extend(Selector, {
a.push(node);
return a;
},
// marks an array of nodes for counting
mark: function(nodes) {
var _true = Prototype.emptyFunction;
@ -359,12 +411,34 @@ Object.extend(Selector, {
node._countedByPrototype = _true;
return nodes;
},
unmark: function(nodes) {
for (var i = 0, node; node = nodes[i]; i++)
node._countedByPrototype = undefined;
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
@ -381,36 +455,37 @@ Object.extend(Selector, {
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) {
// use `typeof` operator to prevent errors
if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
return Prototype.LegacySelector.handlers.unmark(results);
},
// COMBINATOR FUNCTIONS
descendant: function(nodes) {
var h = Selector.handlers;
var h = Prototype.LegacySelector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
h.concat(results, node.getElementsByTagName('*'));
return results;
},
child: function(nodes) {
var h = Selector.handlers;
var h = Prototype.LegacySelector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++) {
for (var j = 0, child; child = node.childNodes[j]; j++)
if (child.nodeType == 1 && child.tagName != '!') results.push(child);
}
return results;
},
adjacent: function(nodes) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
var next = this.nextElementSibling(node);
@ -418,9 +493,9 @@ Object.extend(Selector, {
}
return results;
},
laterSibling: function(nodes) {
var h = Selector.handlers;
var h = Prototype.LegacySelector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
h.concat(results, Element.nextSiblings(node));
return results;
@ -431,17 +506,17 @@ Object.extend(Selector, {
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;
var results = [], h = Prototype.LegacySelector.handlers;
if (nodes) {
if (combinator) {
// fastlane for ordinary descendant combinators
@ -457,11 +532,26 @@ Object.extend(Selector, {
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];
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') {
@ -472,10 +562,10 @@ Object.extend(Selector, {
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)
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 [];
@ -485,11 +575,11 @@ Object.extend(Selector, {
className: function(nodes, root, className, combinator) {
if (nodes && combinator) nodes = this[combinator](nodes);
return Selector.handlers.byClassName(nodes, root, className);
return Prototype.LegacySelector.handlers.byClassName(nodes, root, className);
},
byClassName: function(nodes, root, className) {
if (!nodes) nodes = Selector.handlers.descendant([root]);
if (!nodes) nodes = Prototype.LegacySelector.handlers.descendant([root]);
var needle = ' ' + className + ' ';
for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
nodeClassName = node.className;
@ -498,21 +588,21 @@ Object.extend(Selector, {
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;
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 = [];
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;
@ -520,59 +610,59 @@ Object.extend(Selector, {
}
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);
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 (Selector.handlers.previousElementSibling(node)) continue;
if (Prototype.LegacySelector.handlers.previousElementSibling(node)) continue;
results.push(node);
}
return results;
},
'last-child': function(nodes, value, root) {
for (var i = 0, results = [], node; node = nodes[i]; i++) {
if (Selector.handlers.nextElementSibling(node)) continue;
if (Prototype.LegacySelector.handlers.nextElementSibling(node)) continue;
results.push(node);
}
return results;
},
'only-child': function(nodes, value, root) {
var h = Selector.handlers;
var h = Prototype.LegacySelector.handlers;
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
results.push(node);
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-child': function(nodes, formula, root) {
return Prototype.LegacySelector.pseudos.nth(nodes, formula, root);
},
'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);
'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 = Selector.pseudos;
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] : [];
@ -581,13 +671,13 @@ Object.extend(Selector, {
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;
var h = Prototype.LegacySelector.handlers, results = [], indexed = [], m;
h.mark(nodes);
for (var i = 0, node; node = nodes[i]; i++) {
if (!node.parentNode._countedByPrototype) {
@ -603,17 +693,17 @@ Object.extend(Selector, {
if (m[1] == "-") m[1] = -1;
var a = m[1] ? Number(m[1]) : 1;
var b = m[2] ? Number(m[2]) : 0;
var indices = Selector.pseudos.getIndices(a, b, nodes.length);
var 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;
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
@ -622,50 +712,48 @@ Object.extend(Selector, {
}
return results;
},
'not': function(nodes, selector, root) {
var h = Selector.handlers, selectorType, m;
var exclusions = new Selector(selector).findElements(root);
var h = Prototype.LegacySelector.handlers, selectorType, m;
var exclusions = new Prototype.LegacySelector(selector).findElements(root);
h.mark(exclusions);
for (var i = 0, results = [], node; node = nodes[i]; i++)
if (!node._countedByPrototype) results.push(node);
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) {
@ -675,26 +763,26 @@ Object.extend(Selector, {
},
matchElements: function(elements, expression) {
var matches = $$(expression), h = Selector.handlers;
var matches = $$(expression), h = Prototype.LegacySelector.handlers;
h.mark(matches);
for (var i = 0, results = [], element; element = elements[i]; i++)
if (element._countedByPrototype) results.push(element);
h.unmark(matches);
return results;
},
findElement: function(elements, expression, index) {
if (Object.isNumber(expression)) {
if (Object.isNumber(expression)) {
index = expression; expression = false;
}
return Selector.matchElements(elements, expression || '*')[index || 0];
return Prototype.LegacySelector.matchElements(elements, expression || '*')[index || 0];
},
findChildElements: function(element, expressions) {
expressions = Selector.split(expressions.join(','));
var results = [], h = Selector.handlers;
expressions = Prototype.LegacySelector.split(expressions.join(','));
var results = [], h = Prototype.LegacySelector.handlers;
for (var i = 0, l = expressions.length, selector; i < l; i++) {
selector = new Selector(expressions[i].strip());
selector = new Prototype.LegacySelector(expressions[i].strip());
h.concat(results, selector.findElements(element));
}
return (l > 1) ? h.unique(results) : results;
@ -702,24 +790,14 @@ Object.extend(Selector, {
});
if (Prototype.Browser.IE) {
Object.extend(Selector.handlers, {
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;
},
// 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

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

1
vendor/nwmatcher/repository vendored Submodule

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

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

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

1
vendor/pdoc vendored Submodule

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

1
vendor/sizzle/repository vendored Submodule

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

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

@ -0,0 +1,34 @@
Prototype._original_property = window.Sizzle;
//= require "repository/sizzle"
Prototype.Selector = (function(engine) {
function extend(elements) {
for (var i = 0, length = elements.length; i < length; i++) {
Element.extend(elements[i]);
}
return elements;
}
function select(selector, scope) {
return extend(engine(selector, scope || document));
}
function match(element, selector) {
return engine.matches(selector, [element]).length == 1;
}
function filter(elements, selector) {
return extend(engine.matches(selector, elements));
}
return {
engine: engine,
select: select,
match: match,
filter: filter
};
})(Sizzle);
// Restore globals.
window.Sizzle = Prototype._original_property;
delete Prototype._original_property;

1
vendor/sprockets vendored Submodule

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

1
vendor/unittest_js vendored Submodule

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