aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Spies2010-06-22 17:09:55 -0700
committerRob Spies2010-06-22 17:09:55 -0700
commit1500e91defa4020bfe9608749b25e585cd1d8e3d (patch)
tree8c2872252b62567dc4eb00f7d7547661d5674c55
parenteaa397c76b7d28343cde9f3a0338b9b0e79197c8 (diff)
parentb129a1094e6b42ed82c3ccecc2f40daaa0a6cb6a (diff)
downloadangular.js-1500e91defa4020bfe9608749b25e585cd1d8e3d.tar.bz2
Merge http://github.com/angular/angular.js into angular
Conflicts: .gitignore
-rw-r--r--.externalToolBuilders/JSTD_Tests.launch9
-rw-r--r--.gitignore11
-rw-r--r--.idea/.gitignore1
-rw-r--r--.idea/.rakeTasks7
-rw-r--r--.idea/encodings.xml5
-rw-r--r--.idea/master.iml9
-rw-r--r--.idea/misc.xml14
-rw-r--r--.idea/modules.xml9
-rw-r--r--.idea/vcs.xml8
-rw-r--r--.project27
-rw-r--r--.settings/.jsdtscope9
-rw-r--r--.settings/org.eclipse.wst.jsdt.ui.superType.container1
-rw-r--r--.settings/org.eclipse.wst.jsdt.ui.superType.name1
-rw-r--r--LICENSE22
-rw-r--r--README.md12
-rw-r--r--Rakefile119
-rw-r--r--TODO.text6
-rw-r--r--css/angular-scenario.css76
-rw-r--r--css/angular.css189
-rw-r--r--css/angular_images/arrow_ascend.pngbin0 -> 3093 bytes
-rw-r--r--css/angular_images/arrow_descend.pngbin0 -> 3076 bytes
-rw-r--r--css/angular_images/arrow_left.gifbin0 -> 102 bytes
-rw-r--r--css/angular_images/arrow_right.gifbin0 -> 102 bytes
-rw-r--r--css/angular_images/indicator-wait.pngbin0 -> 1849 bytes
-rw-r--r--css/angular_images/loader-bar.gifbin0 -> 10819 bytes
-rw-r--r--example/calculator-bootstrap.html21
-rw-r--r--example/calculator-minified_init.html21
-rw-r--r--example/calculator.html21
-rw-r--r--example/index.html11
-rw-r--r--example/memoryLeak.html55
-rw-r--r--example/temp.html13
-rw-r--r--example/tweeter/style.css98
-rw-r--r--example/tweeter/tweeter_addressbook.html80
-rw-r--r--example/tweeter/tweeter_demo.html34
-rw-r--r--example/tweeter/tweeterclient.js36
-rw-r--r--jsTestDriver-jquery.conf24
-rw-r--r--jsTestDriver.conf23
-rw-r--r--jstd.log762
-rw-r--r--lib/compiler-closure/COPYING202
-rw-r--r--lib/compiler-closure/README193
-rw-r--r--lib/compiler-closure/compiler.jarbin0 -> 4237729 bytes
-rw-r--r--lib/jasmine-jstd-adapter/JasmineAdapter.js111
-rw-r--r--lib/jasmine/jasmine-0.10.3.js2331
-rw-r--r--lib/jquery/jquery-1.4.2.js6240
-rw-r--r--lib/jquery/jquery-1.4.2.min.js154
-rw-r--r--lib/jquery/jquery-ui-1.7.1.custom.min.js77
-rwxr-xr-xlib/jsl/jslbin0 -> 519636 bytes
-rwxr-xr-xlib/jsl/jsl.default.conf128
-rw-r--r--lib/jstestdriver/JsTestDriver.jarbin0 -> 3133701 bytes
-rw-r--r--lib/nodeserver/server.js22
-rw-r--r--lib/swfobject/swfobject.js4
-rw-r--r--lib/underscore/underscore-min.js17
-rw-r--r--lib/underscore/underscore.js646
-rwxr-xr-xlib/underscore/update.sh7
-rw-r--r--lib/webtoolkit/webtoolkit.base64.js142
-rwxr-xr-xnodeserver.sh1
-rw-r--r--scenario/Runner-compiled.html9
-rw-r--r--scenario/Runner.html9
-rw-r--r--scenario/application-account.html6
-rw-r--r--scenario/application.html34
-rw-r--r--scenario/cross-site-post/People.json4
-rw-r--r--scenario/cross-site-post/index.html10
-rw-r--r--scenario/datastore-scenarios.js19
-rw-r--r--scenario/datastore.html17
-rw-r--r--scenario/perf.html33
-rw-r--r--scenario/style.css7
-rw-r--r--scenario/widgets-scenario.js25
-rw-r--r--scenario/widgets-scenarios.old49
-rw-r--r--scenario/widgets.html98
-rwxr-xr-xserver.sh1
-rw-r--r--src/Angular.js401
-rw-r--r--src/AngularPublic.js29
-rw-r--r--src/Browser.js130
-rw-r--r--src/Compiler.js212
-rw-r--r--src/JSON.js105
-rw-r--r--src/Parser.js730
-rw-r--r--src/Resource.js140
-rw-r--r--src/Scope.js224
-rw-r--r--src/angular-bootstrap.js70
-rw-r--r--src/angular.prefix24
-rw-r--r--src/angular.suffix9
-rw-r--r--src/apis.js338
-rw-r--r--src/delete/Binder.js356
-rw-r--r--src/delete/Model.js65
-rw-r--r--src/delete/Scope.js407
-rw-r--r--src/delete/Widgets.js806
-rw-r--r--src/directives.js261
-rw-r--r--src/filters.js298
-rw-r--r--src/formatters.js32
-rw-r--r--src/jqLite.js248
-rw-r--r--src/markups.js85
-rw-r--r--src/moveToAngularCom/ControlBar.js72
-rw-r--r--src/moveToAngularCom/DataStore.js330
-rw-r--r--src/moveToAngularCom/Server.js68
-rw-r--r--src/moveToAngularCom/Users.js35
-rw-r--r--src/moveToAngularCom/directivesAngularCom.js29
-rw-r--r--src/scenario/DSL.js63
-rw-r--r--src/scenario/Runner.js161
-rw-r--r--src/scenario/angular.prefix30
-rw-r--r--src/scenario/angular.suffix11
-rw-r--r--src/scenario/bootstrap.js44
-rw-r--r--src/services.js361
-rw-r--r--src/validators.js132
-rw-r--r--src/widgets.js328
-rwxr-xr-xtest.sh2
-rw-r--r--test/AngularSpec.js52
-rw-r--r--test/ApiTest.js256
-rw-r--r--test/BinderTest.js676
-rw-r--r--test/BrowserSpecs.js48
-rw-r--r--test/CompilerSpec.js137
-rw-r--r--test/ConsoleTest.js12
-rw-r--r--test/FiltersTest.js143
-rw-r--r--test/FormattersTest.js37
-rw-r--r--test/JsonTest.js84
-rw-r--r--test/ParserTest.js465
-rw-r--r--test/ResourceSpec.js159
-rw-r--r--test/ScenarioSpec.js51
-rw-r--r--test/ScopeSpec.js181
-rw-r--r--test/ValidatorsTest.js169
-rw-r--r--test/angular-mocks.js101
-rw-r--r--test/delete/ScopeTest.js145
-rw-r--r--test/delete/WidgetsTest.js268
-rw-r--r--test/directivesSpec.js226
-rw-r--r--test/jquery_alias.js1
-rw-r--r--test/jquery_remove.js1
-rw-r--r--test/markupSpec.js138
-rw-r--r--test/moveToAngularCom/Base64Test.js5
-rw-r--r--test/moveToAngularCom/DataStoreTest.js616
-rw-r--r--test/moveToAngularCom/EntityDeclarationTest.js50
-rw-r--r--test/moveToAngularCom/FileControllerTest.js98
-rw-r--r--test/moveToAngularCom/MiscTest.js35
-rw-r--r--test/moveToAngularCom/ModelTest.js84
-rw-r--r--test/moveToAngularCom/ServerTest.js42
-rw-r--r--test/moveToAngularCom/UsersTest.js26
-rw-r--r--test/moveToAngularCom/miscTest.js35
-rw-r--r--test/scenario/DSLSpec.js55
-rw-r--r--test/scenario/RunnerSpec.js190
-rw-r--r--test/servicesSpec.js362
-rw-r--r--test/testabilityPatch.js180
-rw-r--r--test/widgetsSpec.js441
140 files changed, 24534 insertions, 1 deletions
diff --git a/.externalToolBuilders/JSTD_Tests.launch b/.externalToolBuilders/JSTD_Tests.launch
new file mode 100644
index 00000000..503cbaff
--- /dev/null
+++ b/.externalToolBuilders/JSTD_Tests.launch
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
+<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1262905463390_2&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/test&quot; type=&quot;2&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js}/test.sh"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
+<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
+</launchConfiguration>
diff --git a/.gitignore b/.gitignore
index 2b10d121..d9989b4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.git4_perforce_config
blaze-eclipse
google3/blaze-*
@@ -21,4 +22,12 @@ google3/.gwt-tmp
google3/alloc
google3tomcat
google3/mbin
-google3/mgenfiles \ No newline at end of file
+google3/mgenfiles
+=======
+angular-minified.map
+externs.js
+angular.js
+angular-minified.js
+angular-debug.js
+angular-scenario.js
+>>>>>>> b129a1094e6b42ed82c3ccecc2f40daaa0a6cb6a
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..a7c382ed
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1 @@
+workspace.xml
diff --git a/.idea/.rakeTasks b/.idea/.rakeTasks
new file mode 100644
index 00000000..50fb6fec
--- /dev/null
+++ b/.idea/.rakeTasks
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Settings><!--This file was automatically generated by Ruby plugin.
+You are allowed to:
+1. Remove rake task
+2. Add existing rake tasks
+To add existing rake tasks automatically delete this file and reload the project.
+--><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Compile JavaScript" fullCmd="compile" taksId="compile" /><RakeTask description="Generate Externs" fullCmd="compileexterns" taksId="compileexterns" /><RakeTask description="Lint" fullCmd="lint" taksId="lint" /><RakeGroup description="" fullCmd="" taksId="server"><RakeTask description="Run JsTestDriver Server" fullCmd="server:start" taksId="start" /><RakeTask description="Run JavaScript tests against the server" fullCmd="server:test" taksId="test" /></RakeGroup><RakeTask description="Run JavaScript tests" fullCmd="test" taksId="test" /><RakeTask description="" fullCmd="default" taksId="default" /></RakeGroup></Settings>
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 00000000..e206d70d
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+</project>
+
diff --git a/.idea/master.iml b/.idea/master.iml
new file mode 100644
index 00000000..8f7472a8
--- /dev/null
+++ b/.idea/master.iml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="RUBY_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$" />
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..bf08d02d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="DependencyValidationManager">
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </component>
+ <component name="ProjectDetails">
+ <option name="projectName" value="master" />
+ </component>
+ <component name="ProjectRootManager" version="2" project-jdk-name="Ruby SDK 1.8.7 (/usr/bin/ruby)" project-jdk-type="RUBY_SDK" />
+ <component name="SvnBranchConfigurationManager">
+ <option name="mySupportsUserInfoFilter" value="true" />
+ </component>
+</project>
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..12b24804
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/master.iml" filepath="$PROJECT_DIR$/.idea/master.iml" />
+ </modules>
+ </component>
+</project>
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..9d32e507
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="" />
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project>
+
diff --git a/.project b/.project
new file mode 100644
index 00000000..0fb4c323
--- /dev/null
+++ b/.project
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>angular.js</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
+ <triggers>auto,full,incremental,</triggers>
+ <arguments>
+ <dictionary>
+ <key>LaunchConfigHandle</key>
+ <value>&lt;project&gt;/.externalToolBuilders/JSTD_Tests.launch</value>
+ </dictionary>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+ </natures>
+</projectDescription>
diff --git a/.settings/.jsdtscope b/.settings/.jsdtscope
new file mode 100644
index 00000000..7beec24e
--- /dev/null
+++ b/.settings/.jsdtscope
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry excluding="test/" kind="src" path="src"/>
+ <classpathentry kind="src" path="src/test"/>
+ <classpathentry excluding="test/" kind="src" path="test"/>
+ <classpathentry kind="src" path="test/test"/>
+ <classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
+</classpath>
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.container b/.settings/org.eclipse.wst.jsdt.ui.superType.container
new file mode 100644
index 00000000..49c8cd4f
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.container
@@ -0,0 +1 @@
+org.eclipse.wst.jsdt.launching.JRE_CONTAINER \ No newline at end of file
diff --git a/.settings/org.eclipse.wst.jsdt.ui.superType.name b/.settings/org.eclipse.wst.jsdt.ui.superType.name
new file mode 100644
index 00000000..11006e2a
--- /dev/null
+++ b/.settings/org.eclipse.wst.jsdt.ui.superType.name
@@ -0,0 +1 @@
+Global \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..b6ad6d3f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+
+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.
+
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..28aadac1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+Angular
+======
+
+Compiling
+---------
+ rake compile
+
+Running Tests
+-------------
+ rake server:start
+ rake test
+
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 00000000..c8de5b78
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,119 @@
+include FileUtils
+
+task :default => [:compile, :test]
+
+desc 'Generate Externs'
+task :compile_externs do
+ out = File.new("externs.js", "w")
+
+ out.write("function _(){};\n")
+ file = File.new("lib/underscore/underscore.js", "r")
+ while (line = file.gets)
+ if line =~ /^\s*_\.(\w+)\s*=.*$/
+ out.write("_.#{$1}=function(){};\n")
+ end
+ end
+ file.close
+
+ out.write("function jQuery(){};\n")
+ file = File.new("lib/jquery/jquery-1.4.2.js", "r")
+ while (line = file.gets)
+ if line =~ /^\s*(\w+)\s*:\s*function.*$/
+ out.write("jQuery.#{$1}=function(){};\n")
+ end
+ end
+ file.close
+ out.write("jQuery.scope=function(){};\n")
+ out.write("jQuery.controller=function(){};\n")
+
+ out.close
+end
+
+desc 'Compile Scenario'
+task :compile_scenario do
+ concat = %x(cat \
+ lib/underscore/underscore.js \
+ lib/jquery/jquery-1.4.2.js \
+ src/scenario/angular.prefix \
+ src/Angular.js \
+ src/JSON.js \
+ src/Scope.js \
+ src/Parser.js \
+ src/Resource.js \
+ src/Browser.js \
+ src/apis.js \
+ src/services.js \
+ src/AngularPublic.js \
+ src/scenario/Runner.js \
+ src/scenario/DSL.js \
+ src/scenario/angular.suffix \
+ )
+ css = %x(cat css/angular-scenario.css)
+ f = File.new("angular-scenario.js", 'w')
+ f.write(concat)
+ f.write('document.write(\'<style type="text/css">\n')
+ f.write(css.gsub(/'/, "\\'").gsub(/\n/, "\\n"));
+ f.write('\n</style>\');')
+ f.close
+end
+
+desc 'Compile JavaScript'
+task :compile do
+ Rake::Task['compile_externs'].execute 0
+ Rake::Task['compile_scenario'].execute 0
+
+ concat = %x(cat \
+ src/angular.prefix \
+ src/Angular.js \
+ src/JSON.js \
+ src/Compiler.js \
+ src/Scope.js \
+ src/Parser.js \
+ src/Resource.js \
+ src/Browser.js \
+ src/jqLite.js \
+ src/apis.js \
+ src/filters.js \
+ src/formatters.js \
+ src/validators.js \
+ src/services.js \
+ src/directives.js \
+ src/markups.js \
+ src/widgets.js \
+ src/AngularPublic.js \
+ src/angular.suffix \
+ )
+ f = File.new("angular-debug.js", 'w')
+ f.write(concat)
+ f.close
+
+ %x(java -jar lib/compiler-closure/compiler.jar \
+ --compilation_level ADVANCED_OPTIMIZATIONS \
+ --js angular-debug.js \
+ --externs externs.js \
+ --create_source_map ./angular-minified.map \
+ --js_output_file angular-minified.js)
+end
+
+namespace :server do
+ desc 'Run JsTestDriver Server'
+ task :start do
+ sh %x(java -jar lib/jstestdriver/JsTestDriver.jar --browser open --port 9876)
+ end
+
+ desc "Run JavaScript tests against the server"
+ task :test do
+ sh %(java -jar lib/jstestdriver/JsTestDriver.jar --tests all)
+ end
+end
+
+desc "Run JavaScript tests"
+task :test do
+ sh %(java -jar lib/jstestdriver/JsTestDriver.jar --tests all --browser open --port 9876)
+end
+
+desc 'Lint'
+task :lint do
+ out = %x(lib/jsl/jsl -conf lib/jsl/jsl.default.conf)
+ print out
+end
diff --git a/TODO.text b/TODO.text
new file mode 100644
index 00000000..d4d013a5
--- /dev/null
+++ b/TODO.text
@@ -0,0 +1,6 @@
+* move angular-bootstrap.js out of anugular.js.
+* 'angular' is the official namespace for public API
+ - angular.defaults = {}
+ - var scope = angular.compile(element, options);
+* angular.js is not self boot straping by default.
+* Remove SWFObject
diff --git a/css/angular-scenario.css b/css/angular-scenario.css
new file mode 100644
index 00000000..3960c357
--- /dev/null
+++ b/css/angular-scenario.css
@@ -0,0 +1,76 @@
+@charset "UTF-8";
+/* CSS Document */
+
+#runner {
+ position: absolute;
+ top:5px;
+ left:10px;
+ right:10px;
+ height: 200px;
+}
+
+.console {
+ display: block;
+ overflow: scroll;
+ height: 200px;
+ border: 1px solid black;
+}
+
+#testView {
+ position: absolute;
+ bottom:10px;
+ top:230px;
+ left:10px;
+ right:10px;
+}
+
+#testView iframe {
+ width: 100%;
+ height: 100%;
+}
+
+li.running > span {
+ background-color: yellow;
+}
+
+#runner span {
+ background-color: green;
+}
+
+#runner .fail > span {
+ background-color: red;
+}
+
+.collapsed > ul {
+ display: none;
+}
+
+//////
+
+.run, .info, .error {
+ display: block;
+ padding: 0 1em;
+ font-family: monospace;
+ white-space: pre;
+}
+
+.run {
+ background-color: lightgrey;
+ padding: 0 .2em;
+}
+
+.run.pass {
+ background-color: lightgreen;
+}
+
+.run.fail {
+ background-color: lightred;
+}
+
+.name, .time, .state {
+ padding-right: 2em;
+}
+
+error {
+ color: red;
+} \ No newline at end of file
diff --git a/css/angular.css b/css/angular.css
new file mode 100644
index 00000000..0fb10cdf
--- /dev/null
+++ b/css/angular.css
@@ -0,0 +1,189 @@
+@charset "UTF-8";
+/* CSS Document */
+
+#ng-console {
+ border: thin solid black;
+ font-family: 'courier';
+ font-size: x-small;
+}
+
+#ng-console .ng-console-error {
+ color: red;
+}
+
+#ng-console .ng-console-info {
+ color: blue;
+}
+
+.ng-upload-widget object {
+ align:center;
+}
+
+.ng-upload-widget a {
+ margin-right: .3em;
+}
+
+.ng-upload-widget span {
+ color: #999999;
+ font-size: smaller;
+}
+
+.ng-format-negative {
+ color: red;
+}
+
+.ng-exception {
+ border: 2px solid #FF0000;
+ font-family: "Courier New", Courier, monospace;
+ font-size: smaller;
+}
+
+.ng-validation-error {
+ border: 2px solid #FF0000;
+}
+
+.ng-hidden {
+ display:none;
+}
+
+/*****************
+ * DatePicker
+ *****************/
+
+div.ui-widget {
+ font-size: 11px;
+ }
+
+/*****************
+ * OrderBy
+ *****************/
+.ng-ascend,
+.ng-descend {
+ padding-right: 20px;
+ background-repeat: no-repeat;
+ background-position: right;
+}
+.ng-ascend { background-image: url(angular_images/arrow_ascend.png); }
+.ng-descend { background-image: url(angular_images/arrow_descend.png); }
+
+/*****************
+ * TIP
+ *****************/
+#ng-callout {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 13px;
+ font-weight: normal;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ vertical-align: baseline;
+ background: transparent;
+ text-decoration: none;
+}
+
+#ng-callout .ng-arrow-left{
+ background-image: url(angular_images/arrow_left.gif);
+ background-repeat: no-repeat;
+ background-position: left top;
+ position: absolute;
+ z-index:101;
+ left:-12px;
+ height:23px;
+ width:10px;
+ top:-3px;
+}
+
+#ng-callout .ng-arrow-right{
+ background-image: url(angular_images/arrow_right.gif);
+ background-repeat: no-repeat;
+ background-position: left top;
+ position: absolute;
+ z-index:101;
+ height:23px;
+ width:11px;
+ top:-2px;
+}
+
+#ng-callout {
+ position: absolute;
+ z-index:100;
+ border: 2px solid #CCCCCC;
+ background-color: #fff;
+}
+
+#ng-callout .ng-content{
+ padding:10px 10px 10px 10px;
+ color:#333333;
+}
+
+
+#ng-callout .ng-title{
+ background-color: #CCCCCC;
+ text-align: left;
+ padding-left: 8px;
+ padding-bottom: 5px;
+ padding-top: 2px;
+ font-weight:bold;
+}
+
+
+#ng-spacer {
+ height: 1.2em;
+}
+
+#ng-loading {
+ position: fixed;
+ bottom: 0;
+ height: 1.2em;
+ width: 100%;
+ text-align: center;
+}
+
+/*****************
+ * Login
+ *****************/
+
+#ng-login {
+ z-index: 2000;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding-top: 100px;
+}
+
+#ng-login .ng-login-container {
+ width: 500px;
+ height: 380px;
+ margin: auto;
+ border-top: 5px solid #FFF;
+ border-left: 5px solid #DDD;
+ border-right: 5px solid #777;
+ border-bottom: 5px solid #555;
+ padding: 0 3px 3px 0;
+}
+
+#ng-login .ng-login-container iframe {
+ width: 100%;
+ height: 100%;
+ border: 2px solid black;
+}
+
+
+/*****************
+ * indicators
+ *****************/
+.ng-indicator-wait {
+ display: inline-block;
+ height: 16px;
+ width: 16px;
+ background-image: url("angular_images/indicator-wait.png");
+}
+
+.ng-input-indicator-wait {
+ background-image: url("angular_images/indicator-wait.png");
+ background-position: right;
+ background-repeat: no-repeat;
+}
diff --git a/css/angular_images/arrow_ascend.png b/css/angular_images/arrow_ascend.png
new file mode 100644
index 00000000..dd27b92b
--- /dev/null
+++ b/css/angular_images/arrow_ascend.png
Binary files differ
diff --git a/css/angular_images/arrow_descend.png b/css/angular_images/arrow_descend.png
new file mode 100644
index 00000000..ec1cb5df
--- /dev/null
+++ b/css/angular_images/arrow_descend.png
Binary files differ
diff --git a/css/angular_images/arrow_left.gif b/css/angular_images/arrow_left.gif
new file mode 100644
index 00000000..4c9e5c66
--- /dev/null
+++ b/css/angular_images/arrow_left.gif
Binary files differ
diff --git a/css/angular_images/arrow_right.gif b/css/angular_images/arrow_right.gif
new file mode 100644
index 00000000..3252c359
--- /dev/null
+++ b/css/angular_images/arrow_right.gif
Binary files differ
diff --git a/css/angular_images/indicator-wait.png b/css/angular_images/indicator-wait.png
new file mode 100644
index 00000000..5b33f7e5
--- /dev/null
+++ b/css/angular_images/indicator-wait.png
Binary files differ
diff --git a/css/angular_images/loader-bar.gif b/css/angular_images/loader-bar.gif
new file mode 100644
index 00000000..47adbf03
--- /dev/null
+++ b/css/angular_images/loader-bar.gif
Binary files differ
diff --git a/example/calculator-bootstrap.html b/example/calculator-bootstrap.html
new file mode 100644
index 00000000..c72837dc
--- /dev/null
+++ b/example/calculator-bootstrap.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
+ <script type="text/javascript" src="../src/angular-bootstrap.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ var scope = angular.compile(document);
+ scope.set('a', 3);
+ scope.updateView();
+ });
+ </script>
+ </head>
+ <body>
+ Quantity: <input type="text" name="a" value="2">
+ *
+ Cost: <input type="text" name="b" value="3.4">
+ = {{a * b | currency}}
+ </body>
+</html>
diff --git a/example/calculator-minified_init.html b/example/calculator-minified_init.html
new file mode 100644
index 00000000..4f113f87
--- /dev/null
+++ b/example/calculator-minified_init.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
+ <script type="text/javascript" src="../angular-minified.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ scope = angular.compile(document);
+ scope.set('a', 3);
+ scope.updateView();
+ });
+ </script>
+ </head>
+ <body>
+ Quantity: <input type="text" name="a" value="2">
+ *
+ Cost: <input type="text" name="b" value="3.4">
+ = {{a * b | currency}}
+ </body>
+</html>
diff --git a/example/calculator.html b/example/calculator.html
new file mode 100644
index 00000000..43d013fc
--- /dev/null
+++ b/example/calculator.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
+ <script type="text/javascript" src="../angular.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ var scope = angular.compile(document);
+ scope.set('a', 3);
+ scope.updateView();
+ });
+ </script>
+ </head>
+ <body>
+ Quantity: <input type="text" name="a" value="2">
+ *
+ Cost: <input type="text" name="b" value="3.4">
+ = {{a * b | currency}}
+ </body>
+</html>
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 00000000..12f88ccc
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ </head>
+ <body>
+ <ul>
+ <li><a href="calculator-bootstrap.html">Calculator: Auto Init</a></li>
+ <li><a href="calculator.html">Calculator: Manual Init</a></li>
+ </ul>
+ </body>
+</html>
diff --git a/example/memoryLeak.html b/example/memoryLeak.html
new file mode 100644
index 00000000..9e5f512d
--- /dev/null
+++ b/example/memoryLeak.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
+ <script type="text/javascript" src="../src/angular-bootstrap.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ var scope = angular.compile(document);
+ scope.init();
+ scope.set("add", function(){
+ var partial = $("#partial");
+ //id++;
+ partial.html('<div>{{ error() }}<br/></div>');
+ var scope = angular.compile(partial);
+ scope.set("hello", function (){
+ return 'who dat?';
+ });
+ scope.set("error", function (){
+ this.misko.length;
+ });
+ function XXXXXXXXX(){};
+ scope.set('xxx', new XXXXXXXXX());
+ scope.set("names", ["adam", "misko", "shyam"]);
+ scope.init();
+ });
+ scope.set("remove", function(){
+ var partial = $("#partial");
+ /*
+ partial.find('*').andSelf().each(function(){
+ var scope = $(this).data('scope');
+ if (scope) {
+ delete scope.state;
+ delete scope.widgets;
+ }
+ var cntl = $(this).data('controller');
+ if (cntl) {
+ delete cntl.view;
+ }
+ $(this).removeData();
+ });
+ */
+ partial.removeData('scope');
+ partial.children().remove();
+ });
+ });
+ </script>
+ <link rel="StyleSheet" type="text/css" href="../css/angular.css"/>
+ </head>
+ <body>
+ <input type="button" value="add" ng-click="add()"/>
+ <input type="button" value="remove" ng-click="remove()"/>
+ <div id="partial"></div>
+ </body>
+</html>
diff --git a/example/temp.html b/example/temp.html
new file mode 100644
index 00000000..d07a6948
--- /dev/null
+++ b/example/temp.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script>
+ </head>
+ <body>
+ {{$location.hashSearch.order}} <br/>
+ <input type="radio" name="$location.hashSearch.order" value="A"/> A <br/>
+ <input type="radio" name="$location.hashSearch.order" checked value="B"/> B <br/>
+ <input type="radio" name="$location.hashSearch.order" value="C"/> C <br/>
+ {{$location.hashSearch.order}} <br/>
+ </body>
+</html>
diff --git a/example/tweeter/style.css b/example/tweeter/style.css
new file mode 100644
index 00000000..e8468b6b
--- /dev/null
+++ b/example/tweeter/style.css
@@ -0,0 +1,98 @@
+.loading {display: none;}
+.fetching .loading {display: block;}
+
+a {
+ color: blue;
+}
+
+h1 {
+ background-color: black;
+ margin: 0;
+ padding: .25em;
+ color: white;
+ border-bottom: 5px solid gray;
+}
+
+.box {
+ border: 2px solid gray;
+}
+
+.tweeter {
+ margin-right: 360px;
+}
+
+ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+li {
+ margin: .25em;
+ padding: 2px;
+}
+
+li img {
+ float: left;
+ margin: 2px;
+ margin-right: .5em;
+ max-height: 48px;
+ min-height: 48px;
+}
+
+li.even {
+ background-color: lightgray;
+}
+
+
+.addressbook {
+ float: right;
+ width: 350px;
+}
+
+.addressbook li {
+ font-size: .9em;
+}
+
+.clrleft {
+ clear: left;
+}
+
+.notes {
+ font-size: .8em;
+ color: gray;
+}
+
+.username, .nickname {
+ font-weight: bold;
+}
+
+.editor {
+ padding: 4px;
+}
+
+label {
+ color: gray;
+ display: inline-block;
+ width: 75px;
+ text-align: right;
+ padding: 2px;
+ margin-top: 10px;
+}
+
+.editor input[type=text],
+.editor textarea {
+ width: 230px;
+ vertical-align: text-top;
+}
+
+.editor TEXTAREA {
+ height: 50px;
+}
+
+.debug{
+ font-size: .7em;
+ white-space: pre;
+ padding: 0;
+ margin: 0;
+} \ No newline at end of file
diff --git a/example/tweeter/tweeter_addressbook.html b/example/tweeter/tweeter_addressbook.html
new file mode 100644
index 00000000..4844c035
--- /dev/null
+++ b/example/tweeter/tweeter_addressbook.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css">
+ <link rel="stylesheet" type="text/css" href="../../css/angular.css">
+ <script type="text/javascript" src="../../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../../lib/jquery/jquery-1.4.2.min.js"></script>
+ <script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
+ <script type="text/javascript" src="tweeterclient.js"></script>
+ </head>
+ <body ng-class="status" ng-init="mute={}" ng-watch="$anchor.user: tweets = fetchTweets($anchor.user)">
+ <div class="addressbook box">
+ <h1>Address Book</h1>
+ [ Filter: <input type="text" name="userFilter"/>]
+ <ul>
+ <li ng-repeat="user in users.$filter(userFilter).$orderBy('screen_name')" ng-class-even="'even'" ng-class-odd="'odd'">
+ <a href="" ng-click="$anchor.user=user.screen_name"><img src="{{user.profile_image_url}}"/></a>
+ <a href="" ng-click="$anchor.user=user.screen_name">{{user.screen_name}}</a>
+ as <span class="nickname">{{user.name}}</span>
+ [ <a href="#" ng-click="$anchor.edituser=user.screen_name">edit</a>
+ | <a href="#" ng-click="users.$remove(user)">X</a>
+ | <a href="#" ng-click="mute[user.screen_name] = ! mute[user.screen_name]">mute</a>
+ ]
+ <div class="notes">{{user.notes|linky}}</div>
+ <div class="clrleft"></div>
+ </li>
+ </ul>
+ <hr/>
+ <div ng-show="$anchor.edituser" ng-eval="user = users.$find({:$.screen_name == $anchor.edituser})">
+ <div class="editor">
+ <label>Username:</label>
+ <input type="text" name="user.screen_name" disabled="disabled"/>
+ <label>Name:</label>
+ <input type="text" name="user.name"/>
+ <label>Image:</label>
+ <input type="text" name="user.profile_image_url"/>
+ <label>Notes:</label>
+ <textarea type="text" name="user.notes"></textarea>
+
+ <input type="button" ng-click="$anchor.edituser=undefined" value="Close"/>
+ </div>
+ </div>
+ <hr/>
+ <div class="debug">
+mute={{mute|json}}
+
+userFilter={{userFilter|json}}
+
+tweetFilter={{tweetFilter|json}}
+
+$anchor={{$anchor}}
+
+users={{users}}
+
+tweets={{tweets}}
+ </div>
+ </div>
+ <div class="tweeter box">
+ <h1>Tweets: {{$anchor.user}}</h1>
+ [ Filter: <input type="text" name="tweetFilter"/>
+ <span ng-show="$anchor.user">| <a href="#user=">&lt;&lt; All</a></span>
+ ]
+ <div class="loading">Loading...</div>
+ <ul>
+ <li ng-repeat="tweet in tweets.$filter(tweetFilter).$filter({:!mute[$.user.screen_name]})"
+ ng-class-even="'even'" ng-class-odd="'odd'"
+ ng-eval="user = users.$find({: $.screen_name == tweet.user.screen_name}) || tweet.user">
+ <img src="{{user.profile_image_url}}"/>
+ [ <a href="" ng-click="$anchor.user=user.screen_name">{{user.nickname || user.name || user.screen_name }}</a>
+ | <a href="" ng-click="users.$includeIf(user, true)">+</a>
+ ]:
+ {{tweet.text | linky}}
+ <span class="notes">{{tweet.created_at}}</span>
+ <span class="notes">{{user.notes}}</span>
+ <div class="clrleft"></div>
+ </li>
+ </ul>
+ </div>
+ </body>
+</html>
diff --git a/example/tweeter/tweeter_demo.html b/example/tweeter/tweeter_demo.html
new file mode 100644
index 00000000..138d4e2b
--- /dev/null
+++ b/example/tweeter/tweeter_demo.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css">
+ <link rel="stylesheet" type="text/css" href="../../css/angular.css">
+ <script type="text/javascript" src="../../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../../lib/jquery/jquery-1.4.2.min.js"></script>
+ <script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
+ <script type="text/javascript" src="tweeterclient.js"></script>
+ </head>
+ <body ng-class="status" Xng-init="tweets = fetchTweets()">
+ (TODO: I should fetch current tweets)
+ <div class="tweeter box">
+ <h1>Tweets: {{$anchor.user}}</h1>
+ [ Filter: <input type="text" name="tweetFilter"/> (TODO: this should act as search box)
+ <span ng-show="$anchor.user">| <a href="#user=">&lt;&lt; All</a></span>
+ ]
+ <div class="loading">Loading...</div>
+ <ul>
+ <li Xng-repeat="tweet in tweets"
+ ng-class-even="'even'" ng-class-odd="'odd'">
+ <img src="{{tweet.user.profile_image_url}}"/>
+ [ <a href="" Xng-click="$anchor.user=tweet.user.screen_name">{{tweet.user.nickname || tweet.user.name || tweet.user.screen_name }}</a>
+ ]:
+ {{tweet.text}} (TODO: I want urls as links)
+ <span class="notes">{{tweet.created_at}}</span>
+ <span class="notes">{{tweet.user.notes}}</span>
+ <div class="clrleft"></div>
+ </li>
+ </ul>
+ </div>
+ <pre>tweets=(TODO: display me!!!)</pre>
+ </body>
+</html>
diff --git a/example/tweeter/tweeterclient.js b/example/tweeter/tweeterclient.js
new file mode 100644
index 00000000..84fc5ef7
--- /dev/null
+++ b/example/tweeter/tweeterclient.js
@@ -0,0 +1,36 @@
+function noop(){}
+$(document).ready(function(){
+ function xhr(method, url, data, callback){
+ jQuery.getJSON(url, function(){
+ callback.apply(this, arguments);
+ scope.updateView();
+ });
+ }
+
+ var resourceFactory = new ResourceFactory(xhr);
+
+ var Tweeter = resourceFactory.route("http://twitter.com/statuses/:service:username.json", {}, {
+ home: {method:'GET', params: {service:'home_timeline'}, isArray:true },
+ user: {method:'GET', params: {service:'user_timeline/'}, isArray:true }
+ });
+
+
+ var scope = window.scope = angular.compile(document, {
+ location:angular.startUrlWatcher()
+ });
+
+ function fetchTweets(username){
+ return username ? Tweeter.user({username: username}) : Tweeter.home();
+ }
+
+ scope.set('fetchTweets', fetchTweets);
+ scope.set('users', [
+ {screen_name:'mhevery', name:'Mi\u0161ko Hevery',
+ notes:'Author of <angular/> http://www.getangular.com.',
+ profile_image_url:'http://a3.twimg.com/profile_images/54360179/Me_-_Small_Banner_normal.jpg'},
+ {screen_name:'abrons', name:'Adam Abrons',
+ notes:'Author of <angular/> & Ruby guru see: http://www.angularjs.org.',
+ profile_image_url:'http://media.linkedin.com/mpr/mpr/shrink_80_80/p/2/000/005/0a8/044278d.jpg'}
+ ]);
+ scope.init();
+});
diff --git a/jsTestDriver-jquery.conf b/jsTestDriver-jquery.conf
new file mode 100644
index 00000000..34538bce
--- /dev/null
+++ b/jsTestDriver-jquery.conf
@@ -0,0 +1,24 @@
+server: http://localhost:9876
+
+load:
+ - lib/jasmine/jasmine-0.10.3.js
+ - lib/jasmine-jstd-adapter/JasmineAdapter.js
+ - lib/jquery/jquery-1.4.2.js
+ - test/jquery_alias.js
+ - src/Angular.js
+ - src/*.js
+ - src/scenario/Runner.js
+ - src/scenario/*.js
+ - test/testabilityPatch.js
+ - test/angular-mocks.js
+ - test/scenario/*.js
+ - test/*.js
+
+exclude:
+ - src/angular.prefix
+ - src/angular.suffix
+ - src/angular-bootstrap.js
+ - src/AngularPublic.js
+ - src/scenario/bootstrap.js
+ - test/jquery_remove.js
+
diff --git a/jsTestDriver.conf b/jsTestDriver.conf
new file mode 100644
index 00000000..16bcf1db
--- /dev/null
+++ b/jsTestDriver.conf
@@ -0,0 +1,23 @@
+server: http://localhost:9876
+
+load:
+ - lib/jasmine/jasmine-0.10.3.js
+ - lib/jasmine-jstd-adapter/JasmineAdapter.js
+ - lib/jquery/jquery-1.4.2.js
+ - test/jquery_remove.js
+ - src/Angular.js
+ - src/*.js
+ - src/scenario/Runner.js
+ - src/scenario/*.js
+ - test/testabilityPatch.js
+ - test/angular-mocks.js
+ - test/scenario/*.js
+ - test/*.js
+
+exclude:
+ - test/jquery_alias.js
+ - src/angular.prefix
+ - src/angular.suffix
+ - src/angular-bootstrap.js
+ - src/scenario/bootstrap.js
+ - src/AngularPublic.js
diff --git a/jstd.log b/jstd.log
new file mode 100644
index 00000000..950b6ce4
--- /dev/null
+++ b/jstd.log
@@ -0,0 +1,762 @@
+Apr 8, 2010 2:04:32 PM com.google.jstestdriver.ServerStartupAction run
+INFO: Starting server...
+Apr 8, 2010 2:04:32 PM org.mortbay.log.Slf4jLog info
+INFO: Transparent ProxyServlet @ forward to http://localhost:9876
+Apr 8, 2010 2:04:33 PM com.google.jstestdriver.BrowserHunter captureBrowser
+INFO: Browser Captured: Firefox version 3.6.3 (1)
+Apr 8, 2010 2:04:33 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1?start HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:04:33 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:04:36 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1?start HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:04:43 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:04:53 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:04 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:14 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:24 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:34 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:44 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:05:54 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:04 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:14 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:24 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:34 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:44 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:06:54 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:07:04 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:07:14 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:07:24 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:07:34 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Pragma: no-cache
+Cache-Control: no-cache
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+
+
+Apr 8, 2010 2:07:44 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:07:55 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:05 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:15 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:25 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:35 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:45 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:08:55 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:05 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:15 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:25 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:35 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:45 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:09:55 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Pragma: no-cache
+Cache-Control: no-cache
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+
+
+Apr 8, 2010 2:10:05 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:15 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:25 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:35 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:45 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Content-Type: text/plain; charset=UTF-8
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:55 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1?start HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+Pragma: no-cache
+Cache-Control: no-cache
+
+
+Apr 8, 2010 2:10:56 PM com.google.jstestdriver.BrowserQueryResponseServlet doPost
+FINEST: POST: POST /query/1 HTTP/1.1
+Host: misko.i:9876
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
+Accept: */*
+Accept-Language: en-us,en;q=0.5
+Accept-Encoding: gzip,deflate
+Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
+Keep-Alive: 115
+Connection: keep-alive
+Referer: http://misko.i:9876/slave/1/Runnerstrict.html
+Pragma: no-cache
+Cache-Control: no-cache
+Content-Type: text/plain; charset=UTF-8
+X-Requested-With: XMLHttpRequest
+Content-Length: 0
+
+
diff --git a/lib/compiler-closure/COPYING b/lib/compiler-closure/COPYING
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/lib/compiler-closure/COPYING
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/compiler-closure/README b/lib/compiler-closure/README
new file mode 100644
index 00000000..af4e6106
--- /dev/null
+++ b/lib/compiler-closure/README
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Contents
+//
+
+The Closure Compiler performs checking, instrumentation, and
+optimizations on JavaScript code. The purpose of this README is to
+explain how to build and run the Closure Compiler.
+
+The Closure Compiler requires Java 6 or higher.
+http://www.java.com/
+
+
+//
+// Building The Closure Compiler
+//
+
+There are three ways to get a Closure Compiler executable.
+
+1) Use one we built for you.
+
+Pre-built Closure binaries can be found at
+http://code.google.com/p/closure-compiler/downloads/list
+
+
+2) Check out the source and build it with Apache Ant.
+
+First, check out the full source tree of the Closure Compiler. There
+are instructions on how to do this at the project site.
+http://code.google.com/p/closure-compiler/source/checkout
+
+Apache Ant is a cross-platform build tool.
+http://ant.apache.org/
+
+At the root of the source tree, there is an Ant file named
+build.xml. To use it, navigate to the same directory and type the
+command
+
+ant jar
+
+This will produce a jar file called "build/compiler.jar".
+
+
+3) Check out the source and build it with Eclipse.
+
+Eclipse is a cross-platform IDE.
+http://www.eclipse.org/
+
+Under Eclipse's File menu, click "New > Project ..." and create a
+"Java Project." You will see an options screen. Give the project a
+name, select "Create project from existing source," and choose the
+root of the checked-out source tree as the existing directory. Verify
+that you are using JRE version 6 or higher.
+
+Eclipse can use the build.xml file to discover rules. When you
+navigate to the build.xml file, you will see all the build rules in
+the "Outline" pane. Run the "jar" rule to build the compiler in
+build/compiler.jar.
+
+
+//
+// Running The Closure Compiler
+//
+
+Once you have the jar binary, running the Closure Compiler is straightforward.
+
+On the command line, type
+
+java -jar compiler.jar
+
+This starts the compiler in interactive mode. Type
+
+var x = 17 + 25;
+
+then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux)
+and "Enter" again. The Compiler will respond:
+
+var x=42;
+
+The Closure Compiler has many options for reading input from a file,
+writing output to a file, checking your code, and running
+optimizations. To learn more, type
+
+java -jar compiler.jar --help
+
+You can read more detailed documentation about the many flags at
+http://code.google.com/closure/compiler/docs/gettingstarted_app.html
+
+
+//
+// Compiling Multiple Scripts
+//
+
+If you have multiple scripts, you should compile them all together with
+one compile command.
+
+java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js
+
+The Closure Compiler will concatenate the files in the order they're
+passed at the command line.
+
+If you need to compile many, many scripts together, you may start to
+run into problems with managing dependencies between scripts. You
+should check out the Closure Library. It contains functions for
+enforcing dependencies between scripts, and a tool called calcdeps.py
+that knows how to give scripts to the Closure Compiler in the right
+order.
+
+http://code.google.com/p/closure-library/
+
+//
+// Licensing
+//
+
+Unless otherwise stated, all source files are licensed under
+the Apache License, Version 2.0.
+
+
+-----
+Code under:
+src/com/google/javascript/rhino
+test/com/google/javascript/rhino
+
+URL: http://www.mozilla.org/rhino
+Version: 1.5R3, with heavy modifications
+License: Netscape Public License and MPL / GPL dual license
+
+Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an
+implementation of JavaScript for the JVM. The JavaScript parser and
+the parse tree data structures were extracted and modified
+significantly for use by Google's JavaScript compiler.
+
+Local Modifications: The packages have been renamespaced. All code not
+relavant to parsing has been removed. A JSDoc parser and static typing
+system have been added.
+
+
+-----
+Code in:
+lib/libtrunk_rhino_parser_jarjared.jar
+
+URL: http://www.mozilla.org/rhino
+Version: Trunk
+License: Netscape Public License and MPL / GPL dual license
+
+Description: Mozilla Rhino is an implementation of JavaScript for the JVM.
+
+Local Modifications: None. We've used JarJar to renamespace the code
+post-compilation. See:
+http://code.google.com/p/jarjar/
+
+
+-----
+Code in:
+lib/google_common.jar
+
+URL: http://code.google.com/p/guava-libraries/
+Version: Trunk
+License: Apache License 2.0
+
+Description: Google's core Java libraries.
+
+Local Modifications: None.
+
+
+----
+Code in:
+lib/junit.jar
+
+URL: http://sourceforge.net/projects/junit/
+Version: 3.8.1
+License: Common Public License 1.0
+
+Description: A framework for writing and running automated tests in Java.
+
+Local Modifications: None.
+
+
diff --git a/lib/compiler-closure/compiler.jar b/lib/compiler-closure/compiler.jar
new file mode 100644
index 00000000..da053a7d
--- /dev/null
+++ b/lib/compiler-closure/compiler.jar
Binary files differ
diff --git a/lib/jasmine-jstd-adapter/JasmineAdapter.js b/lib/jasmine-jstd-adapter/JasmineAdapter.js
new file mode 100644
index 00000000..0fdc4612
--- /dev/null
+++ b/lib/jasmine-jstd-adapter/JasmineAdapter.js
@@ -0,0 +1,111 @@
+/**
+ * @fileoverview Jasmine JsTestDriver Adapter.
+ * @author ibolmo@gmail.com (Olmo Maldonado)
+ * @author misko@hevery.com (Misko Hevery)
+ */
+
+(function() {
+
+ function bind(_this, _function){
+ return function(){
+ return _function.call(_this);
+ };
+ }
+
+ var currentFrame = frame(null, null);
+
+ function frame(parent, name){
+ var caseName = (parent && parent.caseName ? parent.caseName + " " : '') + (name ? name : '');
+ var frame = {
+ name: name,
+ caseName: caseName,
+ parent: parent,
+ testCase: TestCase(caseName),
+ before: [],
+ after: [],
+ runBefore: function(){
+ if (parent) parent.runBefore.apply(this);
+ for ( var i = 0; i < frame.before.length; i++) {
+ frame.before[i].apply(this);
+ }
+ },
+ runAfter: function(){
+ for ( var i = 0; i < frame.after.length; i++) {
+ frame.after[i].apply(this);
+ }
+ if (parent) parent.runAfter.apply(this);
+ }
+ };
+ return frame;
+ };
+
+ jasmine.Env.prototype.describe = (function(describe){
+ return function(description){
+ currentFrame = frame(currentFrame, description);
+ var val = describe.apply(this, arguments);
+ currentFrame = currentFrame.parent;
+ return val;
+ };
+
+ })(jasmine.Env.prototype.describe);
+
+ var id = 0;
+
+ jasmine.Env.prototype.it = (function(it){
+ return function(desc, itFn){
+ var self = this;
+ var spec = it.apply(this, arguments);
+ var currentSpec = this.currentSpec;
+ if (!currentSpec.$id) {
+ currentSpec.$id = id++;
+ }
+ var frame = this.jstdFrame = currentFrame;
+ var name = 'test that it ' + desc;
+ if (this.jstdFrame.testCase.prototype[name])
+ throw "Spec with name '" + desc + "' already exists.";
+ this.jstdFrame.testCase.prototype[name] = function(){
+ jasmine.getEnv().currentSpec = currentSpec;
+ frame.runBefore.apply(currentSpec);
+ try {
+ itFn.apply(currentSpec);
+ } finally {
+ frame.runAfter.apply(currentSpec);
+ }
+ };
+ return spec;
+ };
+
+ })(jasmine.Env.prototype.it);
+
+
+ jasmine.Env.prototype.beforeEach = (function(beforeEach){
+ return function(beforeEachFunction) {
+ beforeEach.apply(this, arguments);
+ currentFrame.before.push(beforeEachFunction);
+ };
+
+ })(jasmine.Env.prototype.beforeEach);
+
+
+ jasmine.Env.prototype.afterEach = (function(afterEach){
+ return function(afterEachFunction) {
+ afterEach.apply(this, arguments);
+ currentFrame.after.push(afterEachFunction);
+ };
+
+ })(jasmine.Env.prototype.afterEach);
+
+
+ jasmine.NestedResults.prototype.addResult = (function(addResult){
+ return function(result) {
+ addResult.call(this, result);
+ if (result.type != 'MessageResult' && !result.passed()) fail(result.message);
+ };
+
+ })(jasmine.NestedResults.prototype.addResult);
+
+ // Reset environment with overriden methods.
+ jasmine.currentEnv_ = null;
+ jasmine.getEnv();
+
+})();
diff --git a/lib/jasmine/jasmine-0.10.3.js b/lib/jasmine/jasmine-0.10.3.js
new file mode 100644
index 00000000..f309493f
--- /dev/null
+++ b/lib/jasmine/jasmine-0.10.3.js
@@ -0,0 +1,2331 @@
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Default interval for event loop yields. Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return window[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(window, 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(window, 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(window, 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(window, 'clearInterval');
+
+jasmine.MessageResult = function(text) {
+ this.type = 'MessageResult';
+ this.text = text;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'ExpectationResult';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+
+ /** @deprecated */
+ this.details = params.details;
+
+ this.message = this.passed_ ? 'Passed.' : params.message;
+ this.trace = this.passed_ ? '' : new Error(this.message);
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj['nodeType'] > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).wasCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs)
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).wasCalled();
+ * expect(foo.not).wasCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length == 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+jasmine.log = function(message) {
+ jasmine.getEnv().currentSpec.log(message);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+
+/**
+ * Waits for a timeout before moving to the next runs()-defined block.
+ * @param {Number} timeout
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next runs()-defined block.
+ *
+ * @param {Number} timeout
+ * @param {Function} latchFunction
+ * @param {String} message
+ */
+var waitsFor = function(timeout, latchFunction, message) {
+ jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message);
+};
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ } catch(e) {
+ }
+ try {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {
+ }
+ throw new Error("This browser does not support XMLHttpRequest.");
+} : XMLHttpRequest;
+
+/**
+ * Adds suite files to an HTML document so that they are executed, thus adding them to the current
+ * Jasmine environment.
+ *
+ * @param {String} url path to the file to include
+ * @param {Boolean} opt_global
+ * @deprecated We suggest you use a different method of including JS source files. <code>jasmine.include</code> will be removed soon.
+ */
+jasmine.include = function(url, opt_global) {
+ if (opt_global) {
+ document.write('<script type="text/javascript" src="' + url + '"></' + 'script>');
+ } else {
+ var xhr;
+ try {
+ xhr = new jasmine.XmlHttpRequest();
+ xhr.open("GET", url, false);
+ xhr.send(null);
+ } catch(e) {
+ throw new Error("couldn't fetch " + url + ": " + e);
+ }
+
+ return eval(xhr.responseText);
+ }
+};
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass;
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (jasmine.version_) {
+ var version = this.version();
+ return version.major + "." + version.minor + "." + version.build + " revision " + version.revision;
+ } else {
+ return "version unknown";
+ }
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ specDefinitions.call(suite);
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj != null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length == 0 && mismatchValues.length == 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+ if (isSuite) {
+ var specs = suiteOrSpec.specs();
+ for (var i = 0; i < specs.length; i++) {
+ summary.children.push(this.summarize_(specs[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.text,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ };
+
+ var summaryResult = {
+ result : result.result,
+ messages : summaryMessages
+ };
+
+ return summaryResult;
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+ this.report();
+};
+
+/** @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. */
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ // todo: report a deprecation warning [xw]
+
+ if (this.isNot) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+ }
+
+ this.reportWasCalled_ = true;
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: result,
+ message: failing_message,
+ details: details
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return result;
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return result;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.wasCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasCalled does not take arguments, use wasCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return "Expected spy " + this.actual.identity + " to have been called.";
+ };
+
+ return this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return "Expected spy " + this.actual.identity + " to not have been called.";
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.wasCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount == 0) {
+ return "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
+ } else {
+ return "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall);
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was";
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function to throw", expected.message || expected, ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = ["reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecResults", "log"];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Tracks a result's message.
+ * @param message
+ */
+jasmine.NestedResults.prototype.log = function(message) {
+ this.items_.push(new jasmine.MessageResult(message));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'MessageResult') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value.navigator && value.frames && value.setTimeout) {
+ this.emitScalar('<window>');
+ } else if (value instanceof jasmine.Matchers.Any) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('<getter>');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/** JasmineReporters.reporter
+ * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to
+ * descendants of this object to do something with the results (see json_reporter.js)
+ *
+ * @deprecated
+ */
+jasmine.Reporters = {};
+
+/**
+ * @deprecated
+ * @param callbacks
+ */
+jasmine.Reporters.reporter = function(callbacks) {
+ /**
+ * @deprecated
+ * @param callbacks
+ */
+ var that = {
+ callbacks: callbacks || {},
+
+ doCallback: function(callback, results) {
+ if (callback) {
+ callback(results);
+ }
+ },
+
+ reportRunnerResults: function(runner) {
+ that.doCallback(that.callbacks.runnerCallback, runner);
+ },
+ reportSuiteResults: function(suite) {
+ that.doCallback(that.callbacks.suiteCallback, suite);
+ },
+ reportSpecResults: function(spec) {
+ that.doCallback(that.callbacks.specCallback, spec);
+ },
+ log: function (str) {
+ if (console && console.log) console.log(str);
+ }
+ };
+
+ return that;
+};
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.push(beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.push(afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.Spec.prototype.log = function(message) {
+ return this.results_.log(message);
+};
+
+/** @deprecated */
+jasmine.Spec.prototype.getResults = function() {
+ return this.results_;
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, timeoutMessage) {
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, timeout, latchFunction, timeoutMessage, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception'
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+ this.env.reporter.log('>> Jasmine Running ' + this.suite.description + ' ' + this.description + '...');
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.push(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.push(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.env.currentRunner().addSuite(block);
+ } else {
+ this.specs_.push(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 100;
+
+jasmine.WaitsForBlock.prototype.execute = function (onComplete) {
+ var self = this;
+ self.env.reporter.log('>> Jasmine waiting for ' + (self.message || 'something to happen'));
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = self.latchFunction.apply(self.spec);
+ } catch (e) {
+ self.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) {
+ var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.message || 'something to happen');
+ self.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+ self.spec._next();
+ } else {
+ self.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ self.env.setTimeout(function () { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: window.setTimeout,
+ clearTimeout: window.clearTimeout,
+ setInterval: window.setInterval,
+ clearInterval: window.clearInterval
+ },
+
+ assertInstalled: function() {
+ if (jasmine.Clock.installed != jasmine.Clock.defaultFakeTimer) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+window.setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+window.setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+window.clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+window.clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+
+jasmine.version_= {
+ "major": 0,
+ "minor": 10,
+ "build": 3,
+ "revision": 1270162784
+ };
diff --git a/lib/jquery/jquery-1.4.2.js b/lib/jquery/jquery-1.4.2.js
new file mode 100644
index 00000000..fff67764
--- /dev/null
+++ b/lib/jquery/jquery-1.4.2.js
@@ -0,0 +1,6240 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function( window, undefined ) {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // (both of which we optimize for)
+ quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,
+
+ // Is it a simple selector
+ isSimple = /^.[^:#\[\.,]*$/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // Has the ready events already been bound?
+ readyBound = false,
+
+ // The functions to execute on DOM ready
+ readyList = [],
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwnProperty = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ indexOf = Array.prototype.indexOf;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = "body";
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ doc = (context ? context.ownerDocument || context : document);
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = buildFragment( [ match[1] ], [ doc ] );
+ selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ if ( elem ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $("TAG")
+ } else if ( !context && /^\w+$/.test( selector ) ) {
+ this.selector = selector;
+ this.context = document;
+ selector = document.getElementsByTagName( selector );
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return (context || rootjQuery).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return jQuery( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if (selector.selector !== undefined) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.4.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady ) {
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ } else if ( readyList ) {
+ // Add the function to the wait list
+ readyList.push( fn );
+ }
+
+ return this;
+ },
+
+ eq: function( i ) {
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, +i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || jQuery(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging object literal values or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
+ var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
+ : jQuery.isArray(copy) ? [] : {};
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 13 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( readyList ) {
+ // Execute all of them
+ var fn, i = 0;
+ while ( (fn = readyList[ i++ ]) ) {
+ fn.call( document, jQuery );
+ }
+
+ // Reset the list of functions
+ readyList = null;
+ }
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyBound ) {
+ return;
+ }
+
+ readyBound = true;
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ return jQuery.ready();
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent("onreadystatechange", DOMContentLoaded);
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return toString.call(obj) === "[object Function]";
+ },
+
+ isArray: function( obj ) {
+ return toString.call(obj) === "[object Array]";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) {
+ return false;
+ }
+
+ // Not own constructor property must be Object
+ if ( obj.constructor
+ && !hasOwnProperty.call(obj, "constructor")
+ && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwnProperty.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw msg;
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@")
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]")
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) {
+
+ // Try to use the native JSON parser first
+ return window.JSON && window.JSON.parse ?
+ window.JSON.parse( data ) :
+ (new Function("return " + data))();
+
+ } else {
+ jQuery.error( "Invalid JSON: " + data );
+ }
+ },
+
+ noop: function() {},
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test(data) ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+
+ if ( jQuery.support.scriptEval ) {
+ script.appendChild( document.createTextNode( data ) );
+ } else {
+ script.text = data;
+ }
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction(object);
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
+ }
+ }
+
+ return object;
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // The extra typeof function check is to prevent crashes
+ // in Safari 2 (See: #3039)
+ if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length, j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ if ( !inv !== !callback( elems[ i ], i ) ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var ret = [], value;
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ proxy: function( fn, proxy, thisObject ) {
+ if ( arguments.length === 2 ) {
+ if ( typeof proxy === "string" ) {
+ thisObject = fn;
+ fn = thisObject[ proxy ];
+ proxy = undefined;
+
+ } else if ( proxy && !jQuery.isFunction( proxy ) ) {
+ thisObject = proxy;
+ proxy = undefined;
+ }
+ }
+
+ if ( !proxy && fn ) {
+ proxy = function() {
+ return fn.apply( thisObject || this, arguments );
+ };
+ }
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ if ( fn ) {
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+ }
+
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ browser: {}
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+ jQuery.inArray = function( elem, array ) {
+ return indexOf.call( array, elem );
+ };
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+function evalScript( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+}
+
+// Mutifunctional method to get and set values to a collection
+// The value/s can be optionally by executed if its a function
+function access( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+}
+
+function now() {
+ return (new Date).getTime();
+}
+(function() {
+
+ jQuery.support = {};
+
+ var root = document.documentElement,
+ script = document.createElement("script"),
+ div = document.createElement("div"),
+ id = "script" + now();
+
+ div.style.display = "none";
+ div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ var all = div.getElementsByTagName("*"),
+ a = div.getElementsByTagName("a")[0];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return;
+ }
+
+ jQuery.support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: div.firstChild.nodeType === 3,
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText insted)
+ style: /red/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: a.getAttribute("href") === "/a",
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55$/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: div.getElementsByTagName("input")[0].value === "on",
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected,
+
+ parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null,
+
+ // Will be defined later
+ deleteExpando: true,
+ checkClone: false,
+ scriptEval: false,
+ noCloneEvent: true,
+ boxModel: null
+ };
+
+ script.type = "text/javascript";
+ try {
+ script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+ } catch(e) {}
+
+ root.insertBefore( script, root.firstChild );
+
+ // Make sure that the execution of code works by injecting a script
+ // tag with appendChild/createTextNode
+ // (IE doesn't support this, fails, and uses .text instead)
+ if ( window[ id ] ) {
+ jQuery.support.scriptEval = true;
+ delete window[ id ];
+ }
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete script.test;
+
+ } catch(e) {
+ jQuery.support.deleteExpando = false;
+ }
+
+ root.removeChild( script );
+
+ if ( div.attachEvent && div.fireEvent ) {
+ div.attachEvent("onclick", function click() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ jQuery.support.noCloneEvent = false;
+ div.detachEvent("onclick", click);
+ });
+ div.cloneNode(true).fireEvent("onclick");
+ }
+
+ div = document.createElement("div");
+ div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
+
+ var fragment = document.createDocumentFragment();
+ fragment.appendChild( div.firstChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
+
+ // Figure out if the W3C box model works as expected
+ // document.body must exist before we can do this
+ jQuery(function() {
+ var div = document.createElement("div");
+ div.style.width = div.style.paddingLeft = "1px";
+
+ document.body.appendChild( div );
+ jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+ document.body.removeChild( div ).style.display = 'none';
+
+ div = null;
+ });
+
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+ var eventSupported = function( eventName ) {
+ var el = document.createElement("div");
+ eventName = "on" + eventName;
+
+ var isSupported = (eventName in el);
+ if ( !isSupported ) {
+ el.setAttribute(eventName, "return;");
+ isSupported = typeof el[eventName] === "function";
+ }
+ el = null;
+
+ return isSupported;
+ };
+
+ jQuery.support.submitBubbles = eventSupported("submit");
+ jQuery.support.changeBubbles = eventSupported("change");
+
+ // release memory in IE
+ root = script = div = all = a = null;
+})();
+
+jQuery.props = {
+ "for": "htmlFor",
+ "class": "className",
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ tabindex: "tabIndex",
+ usemap: "useMap",
+ frameborder: "frameBorder"
+};
+var expando = "jQuery" + now(), uuid = 0, windowData = {};
+
+jQuery.extend({
+ cache: {},
+
+ expando:expando,
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ "object": true,
+ "applet": true
+ },
+
+ data: function( elem, name, data ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ return;
+ }
+
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache, thisCache;
+
+ if ( !id && typeof name === "string" && data === undefined ) {
+ return null;
+ }
+
+ // Compute a unique ID for the element
+ if ( !id ) {
+ id = ++uuid;
+ }
+
+ // Avoid generating a new cache unless none exists and we
+ // want to manipulate it.
+ if ( typeof name === "object" ) {
+ elem[ expando ] = id;
+ thisCache = cache[ id ] = jQuery.extend(true, {}, name);
+
+ } else if ( !cache[ id ] ) {
+ elem[ expando ] = id;
+ cache[ id ] = {};
+ }
+
+ thisCache = cache[ id ];
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined ) {
+ thisCache[ name ] = data;
+ }
+
+ return typeof name === "string" ? thisCache[ name ] : thisCache;
+ },
+
+ removeData: function( elem, name ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ return;
+ }
+
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( thisCache ) {
+ // Remove the section of cache data
+ delete thisCache[ name ];
+
+ // If we've removed all the data, remove the element's cache
+ if ( jQuery.isEmptyObject(thisCache) ) {
+ jQuery.removeData( elem );
+ }
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ // Completely remove the data cache
+ delete cache[ id ];
+ }
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ if ( typeof key === "undefined" && this.length ) {
+ return jQuery.data( this[0] );
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ }
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else {
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() {
+ jQuery.data( this, key, value );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ if ( !elem ) {
+ return;
+ }
+
+ type = (type || "fx") + "queue";
+ var q = jQuery.data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( !data ) {
+ return q || [];
+ }
+
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery.data( elem, type, jQuery.makeArray(data) );
+
+ } else {
+ q.push( data );
+ }
+
+ return q;
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ), fn = queue.shift();
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift("inprogress");
+ }
+
+ fn.call(elem, function() {
+ jQuery.dequeue(elem, type);
+ });
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function( i, elem ) {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function() {
+ var elem = this;
+ setTimeout(function() {
+ jQuery.dequeue( elem, type );
+ }, time );
+ });
+ },
+
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ }
+});
+var rclass = /[\n\t]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rspecialurl = /href|src|style/,
+ rtype = /(button|input)/i,
+ rfocusable = /(button|input|object|select|textarea)/i,
+ rclickable = /^(a|area)$/i,
+ rradiocheck = /radio|checkbox/;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name, fn ) {
+ return this.each(function(){
+ jQuery.attr( this, name, "" );
+ if ( this.nodeType === 1 ) {
+ this.removeAttribute( name );
+ }
+ });
+ },
+
+ addClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.addClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ var classNames = (value || "").split( rspace );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className ) {
+ elem.className = value;
+
+ } else {
+ var className = " " + elem.className + " ", setClass = elem.className;
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
+ setClass += " " + classNames[c];
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.removeClass( value.call(this, i, self.attr("class")) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ var classNames = (value || "").split(rspace);
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var elem = this[i];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ var className = (" " + elem.className + " ").replace(rclass, " ");
+ for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[c] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value, isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className, i = 0, self = jQuery(this),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery.data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ";
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ if ( value === undefined ) {
+ var elem = this[0];
+
+ if ( elem ) {
+ if ( jQuery.nodeName( elem, "option" ) ) {
+ return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+ }
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery(option).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ }
+
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+
+
+ // Everything else, we just grab the value
+ return (elem.value || "").replace(rreturn, "");
+
+ }
+
+ return undefined;
+ }
+
+ var isFunction = jQuery.isFunction(value);
+
+ return this.each(function(i) {
+ var self = jQuery(this), val = value;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call(this, i, self.val());
+ }
+
+ // Typecast each time if the value is a Function and the appended
+ // value is therefore different each time.
+ if ( typeof val === "number" ) {
+ val += "";
+ }
+
+ if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
+ this.checked = jQuery.inArray( self.val(), val ) >= 0;
+
+ } else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(val);
+
+ jQuery( "option", this ).each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ this.selectedIndex = -1;
+ }
+
+ } else {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ // don't set attributes on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery(elem)[name](value);
+ }
+
+ var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ if ( elem.nodeType === 1 ) {
+ // These attributes require special treatment
+ var special = rspecialurl.test( name );
+
+ // Safari mis-reports the default selected property of an option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name === "selected" && !jQuery.support.optSelected ) {
+ var parent = elem.parentNode;
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ }
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ }
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
+ return elem.getAttributeNode( name ).nodeValue;
+ }
+
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ if ( name === "tabIndex" ) {
+ var attributeNode = elem.getAttributeNode( "tabIndex" );
+
+ return attributeNode && attributeNode.specified ?
+ attributeNode.value :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+
+ return elem[ name ];
+ }
+
+ if ( !jQuery.support.style && notxml && name === "style" ) {
+ if ( set ) {
+ elem.style.cssText = "" + value;
+ }
+
+ return elem.style.cssText;
+ }
+
+ if ( set ) {
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+ }
+
+ var attr = !jQuery.support.hrefNormalized && notxml && special ?
+ // Some attributes require a special call on IE
+ elem.getAttribute( name, 2 ) :
+ elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+ // Using attr for specific style information is now deprecated. Use style instead.
+ return jQuery.style( elem, name, value );
+ }
+});
+var rnamespaces = /\.(.*)$/,
+ fcleanup = function( nm ) {
+ return nm.replace(/[^\w\s\.\|`]/g, function( ch ) {
+ return "\\" + ch;
+ });
+ };
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function( elem, types, handler, data ) {
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) {
+ elem = window;
+ }
+
+ var handleObjIn, handleObj;
+
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure
+ var elemData = jQuery.data( elem );
+
+ // If no elemData is found then we must be trying to bind to one of the
+ // banned noData elements
+ if ( !elemData ) {
+ return;
+ }
+
+ var events = elemData.events = elemData.events || {},
+ eventHandle = elemData.handle, eventHandle;
+
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function() {
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+ jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ }
+
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native events in IE.
+ eventHandle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ var type, i = 0, namespaces;
+
+ while ( (type = types[ i++ ]) ) {
+ handleObj = handleObjIn ?
+ jQuery.extend({}, handleObjIn) :
+ { handler: handler, data: data };
+
+ // Namespaced event handlers
+ if ( type.indexOf(".") > -1 ) {
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+ } else {
+ namespaces = [];
+ handleObj.namespace = "";
+ }
+
+ handleObj.type = type;
+ handleObj.guid = handler.guid;
+
+ // Get the current list of functions bound to this event
+ var handlers = events[ type ],
+ special = jQuery.event.special[ type ] || {};
+
+ // Init the event handler queue
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers.push( handleObj );
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, pos ) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+ elemData = jQuery.data( elem ),
+ events = elemData && elemData.events;
+
+ if ( !elemData || !events ) {
+ return;
+ }
+
+ // types is actually an event object here
+ if ( types && types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Unbind all events for the element
+ if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+ types = types || "";
+
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types );
+ }
+
+ return;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ while ( (type = types[ i++ ]) ) {
+ origType = type;
+ handleObj = null;
+ all = type.indexOf(".") < 0;
+ namespaces = [];
+
+ if ( !all ) {
+ // Namespaced event handlers
+ namespaces = type.split(".");
+ type = namespaces.shift();
+
+ namespace = new RegExp("(^|\\.)" +
+ jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)")
+ }
+
+ eventType = events[ type ];
+
+ if ( !eventType ) {
+ continue;
+ }
+
+ if ( !handler ) {
+ for ( var j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ jQuery.event.remove( elem, origType, handleObj.handler, j );
+ eventType.splice( j--, 1 );
+ }
+ }
+
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+
+ for ( var j = pos || 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( handler.guid === handleObj.guid ) {
+ // remove the given handler for the given type
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ if ( pos == null ) {
+ eventType.splice( j--, 1 );
+ }
+
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+
+ if ( pos != null ) {
+ break;
+ }
+ }
+ }
+
+ // remove generic event handler if no more handlers exist
+ if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ removeEvent( elem, type, elemData.handle );
+ }
+
+ ret = null;
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ var handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ delete elemData.events;
+ delete elemData.handle;
+
+ if ( jQuery.isEmptyObject( elemData ) ) {
+ jQuery.removeData( elem );
+ }
+ }
+ },
+
+ // bubbling is internal
+ trigger: function( event, data, elem /*, bubbling */ ) {
+ // Event object or event type
+ var type = event.type || event,
+ bubbling = arguments[3];
+
+ if ( !bubbling ) {
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[expando] ? event :
+ // Object literal
+ jQuery.extend( jQuery.Event(type), event ) :
+ // Just the event type (string)
+ jQuery.Event(type);
+
+ if ( type.indexOf("!") >= 0 ) {
+ event.type = type = type.slice(0, -1);
+ event.exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Don't bubble custom events when global (to avoid too much overhead)
+ event.stopPropagation();
+
+ // Only trigger if we've ever bound an event for it
+ if ( jQuery.event.global[ type ] ) {
+ jQuery.each( jQuery.cache, function() {
+ if ( this.events && this.events[type] ) {
+ jQuery.event.trigger( event, data, this.handle.elem );
+ }
+ });
+ }
+ }
+
+ // Handle triggering a single element
+
+ // don't do events on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ // Clean up in case it is reused
+ event.result = undefined;
+ event.target = elem;
+
+ // Clone the incoming data, if any
+ data = jQuery.makeArray( data );
+ data.unshift( event );
+ }
+
+ event.currentTarget = elem;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) {
+ handle.apply( elem, data );
+ }
+
+ var parent = elem.parentNode || elem.ownerDocument;
+
+ // Trigger an inline bound script
+ try {
+ if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+ if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
+ event.result = false;
+ }
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (e) {}
+
+ if ( !event.isPropagationStopped() && parent ) {
+ jQuery.event.trigger( event, data, parent, true );
+
+ } else if ( !event.isDefaultPrevented() ) {
+ var target = event.target, old,
+ isClick = jQuery.nodeName(target, "a") && type === "click",
+ special = jQuery.event.special[ type ] || {};
+
+ if ( (!special._default || special._default.call( elem, event ) === false) &&
+ !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
+
+ try {
+ if ( target[ type ] ) {
+ // Make sure that we don't accidentally re-trigger the onFOO events
+ old = target[ "on" + type ];
+
+ if ( old ) {
+ target[ "on" + type ] = null;
+ }
+
+ jQuery.event.triggered = true;
+ target[ type ]();
+ }
+
+ // prevent IE from throwing an error for some elements with some event types, see #3533
+ } catch (e) {}
+
+ if ( old ) {
+ target[ "on" + type ] = old;
+ }
+
+ jQuery.event.triggered = false;
+ }
+ }
+ },
+
+ handle: function( event ) {
+ var all, handlers, namespaces, namespace, events;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+ event.currentTarget = this;
+
+ // Namespaced event handlers
+ all = event.type.indexOf(".") < 0 && !event.exclusive;
+
+ if ( !all ) {
+ namespaces = event.type.split(".");
+ event.type = namespaces.shift();
+ namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ var events = jQuery.data(this, "events"), handlers = events[ event.type ];
+
+ if ( events && handlers ) {
+ // Clone the handlers to prevent manipulation
+ handlers = handlers.slice(0);
+
+ for ( var j = 0, l = handlers.length; j < l; j++ ) {
+ var handleObj = handlers[ j ];
+
+ // Filter the functions by class
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handleObj.handler;
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ var ret = handleObj.handler.apply( this, arguments );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+ fix: function( event ) {
+ if ( event[ expando ] ) {
+ return event;
+ }
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = jQuery.Event( originalEvent );
+
+ for ( var i = this.props.length, prop; i; ) {
+ prop = this.props[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary
+ if ( !event.target ) {
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+ }
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement ) {
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+ }
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) {
+ event.which = event.charCode || event.keyCode;
+ }
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button !== undefined ) {
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+ }
+
+ return event;
+ },
+
+ // Deprecated, use jQuery.guid instead
+ guid: 1E8,
+
+ // Deprecated, use jQuery.proxy instead
+ proxy: jQuery.proxy,
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady,
+ teardown: jQuery.noop
+ },
+
+ live: {
+ add: function( handleObj ) {
+ jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) );
+ },
+
+ remove: function( handleObj ) {
+ var remove = true,
+ type = handleObj.origType.replace(rnamespaces, "");
+
+ jQuery.each( jQuery.data(this, "events").live || [], function() {
+ if ( type === this.origType.replace(rnamespaces, "") ) {
+ remove = false;
+ return false;
+ }
+ });
+
+ if ( remove ) {
+ jQuery.event.remove( this, handleObj.origType, liveHandler );
+ }
+ }
+
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( this.setInterval ) {
+ this.onbeforeunload = eventHandle;
+ }
+
+ return false;
+ },
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ }
+};
+
+var removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ elem.removeEventListener( type, handle, false );
+ } :
+ function( elem, type, handle ) {
+ elem.detachEvent( "on" + type, handle );
+ };
+
+jQuery.Event = function( src ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !this.preventDefault ) {
+ return new jQuery.Event( src );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // timeStamp is buggy for some events on Firefox(#3843)
+ // So we won't rely on the native value
+ this.timeStamp = now();
+
+ // Mark it as fixed
+ this[ expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+ }
+ // otherwise set the returnValue property of the original event to false (IE)
+ e.returnValue = false;
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+
+ // Firefox sometimes assigns relatedTarget a XUL element
+ // which we cannot access the parentNode property of
+ try {
+ // Traverse up the tree
+ while ( parent && parent !== this ) {
+ parent = parent.parentNode;
+ }
+
+ if ( parent !== this ) {
+ // set the correct event type
+ event.type = event.data;
+
+ // handle event if we actually just moused on to a non sub-element
+ jQuery.event.handle.apply( this, arguments );
+ }
+
+ // assuming we've left the element since we most likely mousedover a xul element
+ } catch(e) { }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+ event.type = event.data;
+ jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ setup: function( data ) {
+ jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+ },
+ teardown: function( data ) {
+ jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+ }
+ };
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function( data, namespaces ) {
+ if ( this.nodeName.toLowerCase() !== "form" ) {
+ jQuery.event.add(this, "click.specialSubmit", function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+ return trigger( "submit", this, arguments );
+ }
+ });
+
+ jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+ return trigger( "submit", this, arguments );
+ }
+ });
+
+ } else {
+ return false;
+ }
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialSubmit" );
+ }
+ };
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+ var formElems = /textarea|input|select/i,
+
+ changeFilters,
+
+ getVal = function( elem ) {
+ var type = elem.type, val = elem.value;
+
+ if ( type === "radio" || type === "checkbox" ) {
+ val = elem.checked;
+
+ } else if ( type === "select-multiple" ) {
+ val = elem.selectedIndex > -1 ?
+ jQuery.map( elem.options, function( elem ) {
+ return elem.selected;
+ }).join("-") :
+ "";
+
+ } else if ( elem.nodeName.toLowerCase() === "select" ) {
+ val = elem.selectedIndex;
+ }
+
+ return val;
+ },
+
+ testChange = function testChange( e ) {
+ var elem = e.target, data, val;
+
+ if ( !formElems.test( elem.nodeName ) || elem.readOnly ) {
+ return;
+ }
+
+ data = jQuery.data( elem, "_change_data" );
+ val = getVal(elem);
+
+ // the current data will be also retrieved by beforeactivate
+ if ( e.type !== "focusout" || elem.type !== "radio" ) {
+ jQuery.data( elem, "_change_data", val );
+ }
+
+ if ( data === undefined || val === data ) {
+ return;
+ }
+
+ if ( data != null || val ) {
+ e.type = "change";
+ return jQuery.event.trigger( e, arguments[1], elem );
+ }
+ };
+
+ jQuery.event.special.change = {
+ filters: {
+ focusout: testChange,
+
+ click: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+ return testChange.call( this, e );
+ }
+ },
+
+ // Change has to be called before submit
+ // Keydown will be called before keypress, which is used in submit-event delegation
+ keydown: function( e ) {
+ var elem = e.target, type = elem.type;
+
+ if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+ (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+ type === "select-multiple" ) {
+ return testChange.call( this, e );
+ }
+ },
+
+ // Beforeactivate happens also before the previous element is blurred
+ // with this event you can't trigger a change event, but you can store
+ // information/focus[in] is not needed anymore
+ beforeactivate: function( e ) {
+ var elem = e.target;
+ jQuery.data( elem, "_change_data", getVal(elem) );
+ }
+ },
+
+ setup: function( data, namespaces ) {
+ if ( this.type === "file" ) {
+ return false;
+ }
+
+ for ( var type in changeFilters ) {
+ jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+ }
+
+ return formElems.test( this.nodeName );
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialChange" );
+
+ return formElems.test( this.nodeName );
+ }
+ };
+
+ changeFilters = jQuery.event.special.change.filters;
+}
+
+function trigger( type, elem, args ) {
+ args[0].type = type;
+ return jQuery.event.handle.apply( elem, args );
+}
+
+// Create "bubbling" focus and blur events
+if ( document.addEventListener ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ this.addEventListener( orig, handler, true );
+ },
+ teardown: function() {
+ this.removeEventListener( orig, handler, true );
+ }
+ };
+
+ function handler( e ) {
+ e = jQuery.event.fix( e );
+ e.type = fix;
+ return jQuery.event.handle.call( this, e );
+ }
+ });
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+ jQuery.fn[ name ] = function( type, data, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" ) {
+ for ( var key in type ) {
+ this[ name ](key, data, type[key], fn);
+ }
+ return this;
+ }
+
+ if ( jQuery.isFunction( data ) ) {
+ fn = data;
+ data = undefined;
+ }
+
+ var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
+ jQuery( this ).unbind( event, handler );
+ return fn.apply( this, arguments );
+ }) : fn;
+
+ if ( type === "unload" && name !== "one" ) {
+ this.one( type, data, fn );
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.add( this[i], type, handler, data );
+ }
+ }
+
+ return this;
+ };
+});
+
+jQuery.fn.extend({
+ unbind: function( type, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" && !type.preventDefault ) {
+ for ( var key in type ) {
+ this.unbind(key, type[key]);
+ }
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.remove( this[i], type, fn );
+ }
+ }
+
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.live( types, data, fn, selector );
+ },
+
+ undelegate: function( selector, types, fn ) {
+ if ( arguments.length === 0 ) {
+ return this.unbind( "live" );
+
+ } else {
+ return this.die( types, null, fn, selector );
+ }
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ var event = jQuery.Event( type );
+ event.preventDefault();
+ event.stopPropagation();
+ jQuery.event.trigger( event, data, this[0] );
+ return event.result;
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while ( i < args.length ) {
+ jQuery.proxy( fn, args[ i++ ] );
+ }
+
+ return this.click( jQuery.proxy( fn, function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ }));
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+var liveMap = {
+ focus: "focusin",
+ blur: "focusout",
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+ jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+ var type, i = 0, match, namespaces, preType,
+ selector = origSelector || this.selector,
+ context = origSelector ? this : jQuery( this.context );
+
+ if ( jQuery.isFunction( data ) ) {
+ fn = data;
+ data = undefined;
+ }
+
+ types = (types || "").split(" ");
+
+ while ( (type = types[ i++ ]) != null ) {
+ match = rnamespaces.exec( type );
+ namespaces = "";
+
+ if ( match ) {
+ namespaces = match[0];
+ type = type.replace( rnamespaces, "" );
+ }
+
+ if ( type === "hover" ) {
+ types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+ continue;
+ }
+
+ preType = type;
+
+ if ( type === "focus" || type === "blur" ) {
+ types.push( liveMap[ type ] + namespaces );
+ type = type + namespaces;
+
+ } else {
+ type = (liveMap[ type ] || type) + namespaces;
+ }
+
+ if ( name === "live" ) {
+ // bind live handler
+ context.each(function(){
+ jQuery.event.add( this, liveConvert( type, selector ),
+ { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+ });
+
+ } else {
+ // unbind live handler
+ context.unbind( liveConvert( type, selector ), fn );
+ }
+ }
+
+ return this;
+ }
+});
+
+function liveHandler( event ) {
+ var stop, elems = [], selectors = [], args = arguments,
+ related, match, handleObj, elem, j, i, l, data,
+ events = jQuery.data( this, "events" );
+
+ // Make sure we avoid non-left-click bubbling in Firefox (#3861)
+ if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) {
+ return;
+ }
+
+ event.liveFired = this;
+
+ var live = events.live.slice(0);
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+ selectors.push( handleObj.selector );
+
+ } else {
+ live.splice( j--, 1 );
+ }
+ }
+
+ match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+ for ( i = 0, l = match.length; i < l; i++ ) {
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( match[i].selector === handleObj.selector ) {
+ elem = match[i].elem;
+ related = null;
+
+ // Those two events require additional checking
+ if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+ related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+ }
+
+ if ( !related || related !== elem ) {
+ elems.push({ elem: elem, handleObj: handleObj });
+ }
+ }
+ }
+ }
+
+ for ( i = 0, l = elems.length; i < l; i++ ) {
+ match = elems[i];
+ event.currentTarget = match.elem;
+ event.data = match.handleObj.data;
+ event.handleObj = match.handleObj;
+
+ if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) {
+ stop = false;
+ break;
+ }
+ }
+
+ return stop;
+}
+
+function liveConvert( type, selector ) {
+ return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( fn ) {
+ return fn ? this.bind( name, fn ) : this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+});
+
+// Prevent memory leaks in IE
+// Window isn't included so as not to unbind existing unload events
+// More info:
+// - http://isaacschlueter.com/2006/10/msie-memory-leaks/
+if ( window.attachEvent && !window.addEventListener ) {
+ window.attachEvent("onunload", function() {
+ for ( var id in jQuery.cache ) {
+ if ( jQuery.cache[ id ].handle ) {
+ // Try/Catch is to handle iframes being unloaded, see #4280
+ try {
+ jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+ } catch(e) {}
+ }
+ }
+ });
+}
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ var filter = Expr.filter[ type ], found, item, left = match[1];
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = part.toLowerCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = part.toLowerCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = part.toLowerCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ return match[1].toLowerCase();
+ },
+ CHILD: function(match){
+ if ( match[1] === "nth" ) {
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 === i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ } else {
+ Sizzle.error( "Syntax error, unrecognized expression: " + name );
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ if ( type === "first" ) {
+ return true;
+ }
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first === 0 ) {
+ return diff === 0;
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){
+ return "\\" + (num - 0 + 1);
+ }));
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.sourceIndex ? -1 : 1;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return a.ownerDocument ? -1 : 1;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+function getText( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+ })();
+}
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+var contains = document.compareDocumentPosition ? function(a, b){
+ return !!(a.compareDocumentPosition(b) & 16);
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = getText;
+jQuery.isXMLDoc = isXML;
+jQuery.contains = contains;
+
+return;
+
+window.Sizzle = Sizzle;
+
+})();
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ slice = Array.prototype.slice;
+
+// Implement the identical functionality for filter and not
+var winnow = function( elements, qualifier, keep ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return !!qualifier.call( elem, i, elem ) === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return (elem === qualifier) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+ });
+};
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var ret = this.pushStack( "", "find", selector ), length = 0;
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( var n = length; n < ret.length; n++ ) {
+ for ( var r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.filter( selector, this ).length > 0;
+ },
+
+ closest: function( selectors, context ) {
+ if ( jQuery.isArray( selectors ) ) {
+ var ret = [], cur = this[0], match, matches = {}, selector;
+
+ if ( cur && selectors.length ) {
+ for ( var i = 0, l = selectors.length; i < l; i++ ) {
+ selector = selectors[i];
+
+ if ( !matches[selector] ) {
+ matches[selector] = jQuery.expr.match.POS.test( selector ) ?
+ jQuery( selector, context || this.context ) :
+ selector;
+ }
+ }
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( selector in matches ) {
+ match = matches[selector];
+
+ if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
+ ret.push({ selector: selector, elem: cur });
+ delete matches[selector];
+ }
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ return ret;
+ }
+
+ var pos = jQuery.expr.match.POS.test( selectors ) ?
+ jQuery( selectors, context || this.context ) : null;
+
+ return this.map(function( i, cur ) {
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) {
+ return cur;
+ }
+ cur = cur.parentNode;
+ }
+ return null;
+ });
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ if ( !elem || typeof elem === "string" ) {
+ return jQuery.inArray( this[0],
+ // If it receives a string, the selector is used
+ // If it receives nothing, the siblings are used
+ elem ? jQuery( elem ) : this.parent().children() );
+ }
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context || this.context ) :
+ jQuery.makeArray( selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call(arguments).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [], cur = elem[dir];
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g,
+ rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnocache = /<script|<object|<embed|<option|<style/i,
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5)
+ fcloseTag = function( all, front, tag ) {
+ return rselfClosing.test( tag ) ?
+ all :
+ front + "></" + tag + ">";
+ },
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ };
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( text ) {
+ if ( jQuery.isFunction(text) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ self.text( text.call(this, i, self.text()) );
+ });
+ }
+
+ if ( typeof text !== "object" && text !== undefined ) {
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+ }
+
+ return jQuery.text( this );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append(this);
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ), contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function() {
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery(arguments[0]);
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery(arguments[0]).toArray() );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function() {
+ if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var html = this.outerHTML, ownerDocument = this.ownerDocument;
+ if ( !html ) {
+ var div = ownerDocument.createElement("div");
+ div.appendChild( this.cloneNode(true) );
+ html = div.innerHTML;
+ }
+
+ return jQuery.clean([html.replace(rinlinejQuery, "")
+ // Handle the case in IE 8 where action=/test/> self-closes a tag
+ .replace(/=([^="'>\s]+\/)>/g, '="$1">')
+ .replace(rleadingWhitespace, "")], ownerDocument)[0];
+ } else {
+ return this.cloneNode(true);
+ }
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true ) {
+ cloneCopyEvent( this, ret );
+ cloneCopyEvent( this.find("*"), ret.find("*") );
+ }
+
+ // Return the cloned set
+ return ret;
+ },
+
+ html: function( value ) {
+ if ( value === undefined ) {
+ return this[0] && this[0].nodeType === 1 ?
+ this[0].innerHTML.replace(rinlinejQuery, "") :
+ null;
+
+ // See if we can take a shortcut and just use innerHTML
+ } else if ( typeof value === "string" && !rnocache.test( value ) &&
+ (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+ value = value.replace(rxhtmlTag, fcloseTag);
+
+ try {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( this[i].nodeType === 1 ) {
+ jQuery.cleanData( this[i].getElementsByTagName("*") );
+ this[i].innerHTML = value;
+ }
+ }
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {
+ this.empty().append( value );
+ }
+
+ } else if ( jQuery.isFunction( value ) ) {
+ this.each(function(i){
+ var self = jQuery(this), old = self.html();
+ self.empty().append(function(){
+ return value.call( this, i, old );
+ });
+ });
+
+ } else {
+ this.empty().append( value );
+ }
+
+ return this;
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery(value).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling, parent = this.parentNode;
+
+ jQuery(this).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, value = args[0], scripts = [], fragment, parent;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ i > 0 || results.cacheable || this.length > 1 ?
+ fragment.cloneNode(true) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, evalScript );
+ }
+ }
+
+ return this;
+
+ function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+ }
+ }
+});
+
+function cloneCopyEvent(orig, ret) {
+ var i = 0;
+
+ ret.each(function() {
+ if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) {
+ return;
+ }
+
+ var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( var type in events ) {
+ for ( var handler in events[ type ] ) {
+ jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+ }
+ }
+ }
+ });
+}
+
+function buildFragment( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults,
+ doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
+
+ // Only cache "small" (1/2 KB) strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+ !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+ cacheable = true;
+ cacheresults = jQuery.fragments[ args[0] ];
+ if ( cacheresults ) {
+ if ( cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+}
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [], insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+jQuery.extend({
+ clean: function( elems, context, fragment, scripts ) {
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ var ret = [];
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" && !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+
+ } else if ( typeof elem === "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, fcloseTag);
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ for ( var i = 0; ret[i]; i++ ) {
+ if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+ } else {
+ if ( ret[i].nodeType === 1 ) {
+ ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+ }
+ fragment.appendChild( ret[i] );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id, cache = jQuery.cache,
+ special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ];
+
+ if ( data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ } else {
+ removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+// exclude the following css properties to add px
+var rexclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ ralpha = /alpha\([^)]*\)/,
+ ropacity = /opacity=([^)]*)/,
+ rfloat = /float/i,
+ rdashAlpha = /-([a-z])/ig,
+ rupper = /([A-Z])/g,
+ rnumpx = /^-?\d+(?:px)?$/i,
+ rnum = /^-?\d/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display:"block" },
+ cssWidth = [ "Left", "Right" ],
+ cssHeight = [ "Top", "Bottom" ],
+
+ // cache check for defaultView.getComputedStyle
+ getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
+ // normalize float css property
+ styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat",
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ };
+
+jQuery.fn.css = function( name, value ) {
+ return access( this, name, value, true, function( elem, name, value ) {
+ if ( value === undefined ) {
+ return jQuery.curCSS( elem, name );
+ }
+
+ if ( typeof value === "number" && !rexclude.test(name) ) {
+ value += "px";
+ }
+
+ jQuery.style( elem, name, value );
+ });
+};
+
+jQuery.extend({
+ style: function( elem, name, value ) {
+ // don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return undefined;
+ }
+
+ // ignore negative width and height values #1599
+ if ( (name === "width" || name === "height") && parseFloat(value) < 0 ) {
+ value = undefined;
+ }
+
+ var style = elem.style || elem, set = value !== undefined;
+
+ // IE uses filters for opacity
+ if ( !jQuery.support.opacity && name === "opacity" ) {
+ if ( set ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ var opacity = parseInt( value, 10 ) + "" === "NaN" ? "" : "alpha(opacity=" + value * 100 + ")";
+ var filter = style.filter || jQuery.curCSS( elem, "filter" ) || "";
+ style.filter = ralpha.test(filter) ? filter.replace(ralpha, opacity) : opacity;
+ }
+
+ return style.filter && style.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( ropacity.exec(style.filter)[1] ) / 100) + "":
+ "";
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( rfloat.test( name ) ) {
+ name = styleFloat;
+ }
+
+ name = name.replace(rdashAlpha, fcamelCase);
+
+ if ( set ) {
+ style[ name ] = value;
+ }
+
+ return style[ name ];
+ },
+
+ css: function( elem, name, force, extra ) {
+ if ( name === "width" || name === "height" ) {
+ var val, props = cssShow, which = name === "width" ? cssWidth : cssHeight;
+
+ function getWH() {
+ val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
+
+ if ( extra === "border" ) {
+ return;
+ }
+
+ jQuery.each( which, function() {
+ if ( !extra ) {
+ val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ }
+
+ if ( extra === "margin" ) {
+ val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
+ } else {
+ val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ }
+ });
+ }
+
+ if ( elem.offsetWidth !== 0 ) {
+ getWH();
+ } else {
+ jQuery.swap( elem, props, getWH );
+ }
+
+ return Math.max(0, Math.round(val));
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style, filter;
+
+ // IE uses filters for opacity
+ if ( !jQuery.support.opacity && name === "opacity" && elem.currentStyle ) {
+ ret = ropacity.test(elem.currentStyle.filter || "") ?
+ (parseFloat(RegExp.$1) / 100) + "" :
+ "";
+
+ return ret === "" ?
+ "1" :
+ ret;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( rfloat.test( name ) ) {
+ name = styleFloat;
+ }
+
+ if ( !force && style && style[ name ] ) {
+ ret = style[ name ];
+
+ } else if ( getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( rfloat.test( name ) ) {
+ name = "float";
+ }
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ var defaultView = elem.ownerDocument.defaultView;
+
+ if ( !defaultView ) {
+ return null;
+ }
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle ) {
+ ret = computedStyle.getPropertyValue( name );
+ }
+
+ // We should always get a number back from opacity
+ if ( name === "opacity" && ret === "" ) {
+ ret = "1";
+ }
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(rdashAlpha, fcamelCase);
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = camelCase === "fontSize" ? "1em" : (ret || 0);
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth, height = elem.offsetHeight,
+ skip = elem.nodeName.toLowerCase() === "tr";
+
+ return width === 0 && height === 0 && !skip ?
+ true :
+ width > 0 && height > 0 && !skip ?
+ false :
+ jQuery.curCSS(elem, "display") === "none";
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+var jsc = now(),
+ rscript = /<script(.|\s)*?\/script>/gi,
+ rselectTextarea = /select|textarea/i,
+ rinput = /color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,
+ jsre = /=\?(&|$)/,
+ rquery = /\?/,
+ rts = /(\?|&)_=.*?(&|$)/,
+ rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+ r20 = /%20/g,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load;
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" ) {
+ return _load.call( this, url );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = null;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function( res, status ) {
+ // If successful, inject the HTML into all the matched elements
+ if ( status === "success" || status === "notmodified" ) {
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div />")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(res.responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ res.responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [res.responseText, status, res] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function() {
+ return this.elements ? jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function() {
+ return this.name && !this.disabled &&
+ (this.checked || rselectTextarea.test(this.nodeName) ||
+ rinput.test(this.type));
+ })
+ .map(function( i, elem ) {
+ var val = jQuery(this).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray(val) ?
+ jQuery.map( val, function( val, i ) {
+ return { name: elem.name, value: val };
+ }) :
+ { name: elem.name, value: val };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function( i, o ) {
+ jQuery.fn[o] = function( f ) {
+ return this.bind(o, f);
+ };
+});
+
+jQuery.extend({
+
+ get: function( url, data, callback, type ) {
+ // shift arguments if data argument was omited
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = null;
+ }
+
+ return jQuery.ajax({
+ type: "GET",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get(url, null, callback, "script");
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get(url, data, callback, "json");
+ },
+
+ post: function( url, data, callback, type ) {
+ // shift arguments if data argument was omited
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = {};
+ }
+
+ return jQuery.ajax({
+ type: "POST",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ ajaxSetup: function( settings ) {
+ jQuery.extend( jQuery.ajaxSettings, settings );
+ },
+
+ ajaxSettings: {
+ url: location.href,
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ username: null,
+ password: null,
+ traditional: false,
+ */
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7 (can't request local files),
+ // so we use the ActiveXObject when it is available
+ // This function can be overriden by calling jQuery.ajaxSetup
+ xhr: window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject) ?
+ function() {
+ return new window.XMLHttpRequest();
+ } :
+ function() {
+ try {
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {}
+ },
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ script: "text/javascript, application/javascript",
+ json: "application/json, text/javascript",
+ text: "text/plain",
+ _default: "*/*"
+ }
+ },
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajax: function( origSettings ) {
+ var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings);
+
+ var jsonp, status, data,
+ callbackContext = origSettings && origSettings.context || s,
+ type = s.type.toUpperCase();
+
+ // convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Handle JSONP Parameter Callbacks
+ if ( s.dataType === "jsonp" ) {
+ if ( type === "GET" ) {
+ if ( !jsre.test( s.url ) ) {
+ s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ }
+ } else if ( !s.data || !jsre.test(s.data) ) {
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ }
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
+ jsonp = s.jsonpCallback || ("jsonp" + jsc++);
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data ) {
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ }
+
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+
+ try {
+ delete window[ jsonp ];
+ } catch(e) {}
+
+ if ( head ) {
+ head.removeChild( script );
+ }
+ };
+ }
+
+ if ( s.dataType === "script" && s.cache === null ) {
+ s.cache = false;
+ }
+
+ if ( s.cache === false && type === "GET" ) {
+ var ts = now();
+
+ // try replacing _= if it is there
+ var ret = s.url.replace(rts, "$1_=" + ts + "$2");
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
+ }
+
+ // If data is available, append data to url for get requests
+ if ( s.data && type === "GET" ) {
+ s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
+ }
+
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // Matches an absolute URL, and saves the domain
+ var parts = rurl.exec( s.url ),
+ remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType === "script" && type === "GET" && remote ) {
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
+ var script = document.createElement("script");
+ script.src = s.url;
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ // Handle Script loading
+ if ( !jsonp ) {
+ var done = false;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function() {
+ if ( !done && (!this.readyState ||
+ this.readyState === "loaded" || this.readyState === "complete") ) {
+ done = true;
+ success();
+ complete();
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+ }
+ };
+ }
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object
+ var xhr = s.xhr();
+
+ if ( !xhr ) {
+ return;
+ }
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ } else {
+ xhr.open(type, s.url, s.async);
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ // Set the correct header, if data is being sent
+ if ( s.data || origSettings && origSettings.contentType ) {
+ xhr.setRequestHeader("Content-Type", s.contentType);
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[s.url] ) {
+ xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]);
+ }
+
+ if ( jQuery.etag[s.url] ) {
+ xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]);
+ }
+ }
+
+ // Set header so the called script knows that it's an XMLHttpRequest
+ // Only send the header if it's not a remote XHR
+ if ( !remote ) {
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+ s.accepts[ s.dataType ] + ", */*" :
+ s.accepts._default );
+ } catch(e) {}
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && s.beforeSend.call(callbackContext, xhr, s) === false ) {
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global ) {
+ trigger("ajaxSend", [xhr, s]);
+ }
+
+ // Wait for a response to come back
+ var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) {
+ // The request was aborted
+ if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) {
+ // Opera doesn't call onreadystatechange before this point
+ // so we simulate the call
+ if ( !requestDone ) {
+ complete();
+ }
+
+ requestDone = true;
+ if ( xhr ) {
+ xhr.onreadystatechange = jQuery.noop;
+ }
+
+ // The transfer is complete and the data is available, or the request timed out
+ } else if ( !requestDone && xhr && (xhr.readyState === 4 || isTimeout === "timeout") ) {
+ requestDone = true;
+ xhr.onreadystatechange = jQuery.noop;
+
+ status = isTimeout === "timeout" ?
+ "timeout" :
+ !jQuery.httpSuccess( xhr ) ?
+ "error" :
+ s.ifModified && jQuery.httpNotModified( xhr, s.url ) ?
+ "notmodified" :
+ "success";
+
+ var errMsg;
+
+ if ( status === "success" ) {
+ // Watch for, and catch, XML document parse errors
+ try {
+ // process the data (runs the xml through httpData regardless of callback)
+ data = jQuery.httpData( xhr, s.dataType, s );
+ } catch(err) {
+ status = "parsererror";
+ errMsg = err;
+ }
+ }
+
+ // Make sure that the request was successful or notmodified
+ if ( status === "success" || status === "notmodified" ) {
+ // JSONP handles its own success callback
+ if ( !jsonp ) {
+ success();
+ }
+ } else {
+ jQuery.handleError(s, xhr, status, errMsg);
+ }
+
+ // Fire the complete handlers
+ complete();
+
+ if ( isTimeout === "timeout" ) {
+ xhr.abort();
+ }
+
+ // Stop memory leaks
+ if ( s.async ) {
+ xhr = null;
+ }
+ }
+ };
+
+ // Override the abort handler, if we can (IE doesn't allow it, but that's OK)
+ // Opera doesn't fire onreadystatechange at all on abort
+ try {
+ var oldAbort = xhr.abort;
+ xhr.abort = function() {
+ if ( xhr ) {
+ oldAbort.call( xhr );
+ }
+
+ onreadystatechange( "abort" );
+ };
+ } catch(e) { }
+
+ // Timeout checker
+ if ( s.async && s.timeout > 0 ) {
+ setTimeout(function() {
+ // Check to see if the request is still happening
+ if ( xhr && !requestDone ) {
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send( type === "POST" || type === "PUT" || type === "DELETE" ? s.data : null );
+ } catch(e) {
+ jQuery.handleError(s, xhr, null, e);
+ // Fire the complete handlers
+ complete();
+ }
+
+ // firefox 1.5 doesn't fire statechange for sync requests
+ if ( !s.async ) {
+ onreadystatechange();
+ }
+
+ function success() {
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success ) {
+ s.success.call( callbackContext, data, status, xhr );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ trigger( "ajaxSuccess", [xhr, s] );
+ }
+ }
+
+ function complete() {
+ // Process result
+ if ( s.complete ) {
+ s.complete.call( callbackContext, xhr, status);
+ }
+
+ // The request was completed
+ if ( s.global ) {
+ trigger( "ajaxComplete", [xhr, s] );
+ }
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+
+ function trigger(type, args) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) {
+ s.error.call( s.context || s, xhr, status, e );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] );
+ }
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol === "file:" ||
+ // Opera returns 0 when status is 304
+ ( xhr.status >= 200 && xhr.status < 300 ) ||
+ xhr.status === 304 || xhr.status === 1223 || xhr.status === 0;
+ } catch(e) {}
+
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ var lastModified = xhr.getResponseHeader("Last-Modified"),
+ etag = xhr.getResponseHeader("Etag");
+
+ if ( lastModified ) {
+ jQuery.lastModified[url] = lastModified;
+ }
+
+ if ( etag ) {
+ jQuery.etag[url] = etag;
+ }
+
+ // Opera returns 0 when status is 304
+ return xhr.status === 304 || xhr.status === 0;
+ },
+
+ httpData: function( xhr, type, s ) {
+ var ct = xhr.getResponseHeader("content-type") || "",
+ xml = type === "xml" || !type && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.nodeName === "parsererror" ) {
+ jQuery.error( "parsererror" );
+ }
+
+ // Allow a pre-filtering function to sanitize the response
+ // s is checked to keep backwards compatibility
+ if ( s && s.dataFilter ) {
+ data = s.dataFilter( data, type );
+ }
+
+ // The filter can actually parse the response
+ if ( typeof data === "string" ) {
+ // Get the JavaScript object, if JSON is used.
+ if ( type === "json" || !type && ct.indexOf("json") >= 0 ) {
+ data = jQuery.parseJSON( data );
+
+ // If the type is "script", eval it in global context
+ } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) {
+ jQuery.globalEval( data );
+ }
+ }
+
+ return data;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [];
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray(a) || a.jquery ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[prefix] );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join("&").replace(r20, "+");
+
+ function buildParams( prefix, obj ) {
+ if ( jQuery.isArray(obj) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || /\[\]$/.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v );
+ }
+ });
+
+ } else if ( !traditional && obj != null && typeof obj === "object" ) {
+ // Serialize object item.
+ jQuery.each( obj, function( k, v ) {
+ buildParams( prefix + "[" + k + "]", v );
+ });
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+ }
+
+ function add( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction(value) ? value() : value;
+ s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
+ }
+ }
+});
+var elemdisplay = {},
+ rfxtypes = /toggle|show|hide/,
+ rfxnum = /^([+-]=)?([\d+-.]+)(.*)$/,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ];
+
+jQuery.fn.extend({
+ show: function( speed, callback ) {
+ if ( speed || speed === 0) {
+ return this.animate( genFx("show", 3), speed, callback);
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var old = jQuery.data(this[i], "olddisplay");
+
+ this[i].style.display = old || "";
+
+ if ( jQuery.css(this[i], "display") === "none" ) {
+ var nodeName = this[i].nodeName, display;
+
+ if ( elemdisplay[ nodeName ] ) {
+ display = elemdisplay[ nodeName ];
+
+ } else {
+ var elem = jQuery("<" + nodeName + " />").appendTo("body");
+
+ display = elem.css("display");
+
+ if ( display === "none" ) {
+ display = "block";
+ }
+
+ elem.remove();
+
+ elemdisplay[ nodeName ] = display;
+ }
+
+ jQuery.data(this[i], "olddisplay", display);
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var j = 0, k = this.length; j < k; j++ ) {
+ this[j].style.display = jQuery.data(this[j], "olddisplay") || "";
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, callback);
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ var old = jQuery.data(this[i], "olddisplay");
+ if ( !old && old !== "none" ) {
+ jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( var j = 0, k = this.length; j < k; j++ ) {
+ this[j].style.display = "none";
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete );
+ }
+
+ return this[ optall.queue === false ? "each" : "queue" ](function() {
+ var opt = jQuery.extend({}, optall), p,
+ hidden = this.nodeType === 1 && jQuery(this).is(":hidden"),
+ self = this;
+
+ for ( p in prop ) {
+ var name = p.replace(rdashAlpha, fcamelCase);
+
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ p = name;
+ }
+
+ if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
+ return opt.complete.call(this);
+ }
+
+ if ( ( p === "height" || p === "width" ) && this.style ) {
+ // Store display property
+ opt.display = jQuery.css(this, "display");
+
+ // Make sure that nothing sneaks out
+ opt.overflow = this.style.overflow;
+ }
+
+ if ( jQuery.isArray( prop[p] ) ) {
+ // Create (if needed) and add to specialEasing
+ (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
+ prop[p] = prop[p][0];
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function( name, val ) {
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( rfxtypes.test(val) ) {
+ e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+
+ } else {
+ var parts = rfxnum.exec(val),
+ start = e.cur(true) || 0;
+
+ if ( parts ) {
+ var end = parseFloat( parts[2] ),
+ unit = parts[3] || "px";
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ self.style[ name ] = (end || 1) + unit;
+ start = ((end || 1) / e.cur(true)) * start;
+ self.style[ name ] = start + unit;
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ stop: function( clearQueue, gotoEnd ) {
+ var timers = jQuery.timers;
+
+ if ( clearQueue ) {
+ this.queue([]);
+ }
+
+ this.each(function() {
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- ) {
+ if ( timers[i].elem === this ) {
+ if (gotoEnd) {
+ // force the next step to be the last
+ timers[i](true);
+ }
+
+ timers.splice(i, 1);
+ }
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if ( !gotoEnd ) {
+ this.dequeue();
+ }
+
+ return this;
+ }
+
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show", 1),
+ slideUp: genFx("hide", 1),
+ slideToggle: genFx("toggle", 1),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, callback ) {
+ return this.animate( props, speed, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function() {
+ if ( opt.queue !== false ) {
+ jQuery(this).dequeue();
+ }
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig ) {
+ options.orig = {};
+ }
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+ // Set display property to block for height/width animations
+ if ( ( this.prop === "height" || this.prop === "width" ) && this.elem.style ) {
+ this.elem.style.display = "block";
+ }
+ },
+
+ // Get the current size
+ cur: function( force ) {
+ if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+ return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ this.startTime = now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ var self = this;
+ function t( gotoEnd ) {
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval(jQuery.fx.tick, 13);
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var t = now(), done = true;
+
+ if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ for ( var i in this.options.curAnim ) {
+ if ( this.options.curAnim[i] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ if ( this.options.display != null ) {
+ // Reset the overflow
+ this.elem.style.overflow = this.options.overflow;
+
+ // Reset the display
+ var old = jQuery.data(this.elem, "olddisplay");
+ this.elem.style.display = old ? old : this.options.display;
+
+ if ( jQuery.css(this.elem, "display") === "none" ) {
+ this.elem.style.display = "block";
+ }
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide ) {
+ jQuery(this.elem).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show ) {
+ for ( var p in this.options.curAnim ) {
+ jQuery.style(this.elem, p, this.options.orig[p]);
+ }
+ }
+
+ // Execute the complete function
+ this.options.complete.call( this.elem );
+ }
+
+ return false;
+
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
+ var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
+ this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ ) {
+ if ( !timers[i]() ) {
+ timers.splice(i--, 1);
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style(fx.elem, "opacity", fx.now);
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+if ( "getBoundingClientRect" in document.documentElement ) {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, docElem = doc.documentElement,
+ clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ top = box.top + (self.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop,
+ left = box.left + (self.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ jQuery.offset.initialize();
+
+ var offsetParent = elem.offsetParent, prevOffsetParent = elem,
+ doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+ body = doc.body, defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop, left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.offset = {
+ initialize: function() {
+ var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0,
+ html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+ jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+ container.innerHTML = html;
+ body.insertBefore( container, body.firstChild );
+ innerDiv = container.firstChild;
+ checkDiv = innerDiv.firstChild;
+ td = innerDiv.nextSibling.firstChild.firstChild;
+
+ this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+ this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+ checkDiv.style.position = "fixed", checkDiv.style.top = "20px";
+ // safari subtracts parent border width here which is 5px
+ this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+ checkDiv.style.position = checkDiv.style.top = "";
+
+ innerDiv.style.overflow = "hidden", innerDiv.style.position = "relative";
+ this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+ this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+ body.removeChild( container );
+ body = container = innerDiv = checkDiv = table = td = null;
+ jQuery.offset.initialize = jQuery.noop;
+ },
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop, left = body.offsetLeft;
+
+ jQuery.offset.initialize();
+
+ if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.curCSS(body, "marginTop", true) ) || 0;
+ left += parseFloat( jQuery.curCSS(body, "marginLeft", true) ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ // set position first, in-case top/left are set even on static elem
+ if ( /static/.test( jQuery.curCSS( elem, "position" ) ) ) {
+ elem.style.position = "relative";
+ }
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curTop = parseInt( jQuery.curCSS( elem, "top", true ), 10 ) || 0,
+ curLeft = parseInt( jQuery.curCSS( elem, "left", true ), 10 ) || 0;
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ var props = {
+ top: (options.top - curOffset.top) + curTop,
+ left: (options.left - curOffset.left) + curLeft
+ };
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.curCSS(elem, "marginTop", true) ) || 0;
+ offset.left -= parseFloat( jQuery.curCSS(elem, "marginLeft", true) ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.curCSS(offsetParent[0], "borderTopWidth", true) ) || 0;
+ parentOffset.left += parseFloat( jQuery.curCSS(offsetParent[0], "borderLeftWidth", true) ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+ var method = "scroll" + name;
+
+ jQuery.fn[ method ] = function(val) {
+ var elem = this[0], win;
+
+ if ( !elem ) {
+ return null;
+ }
+
+ if ( val !== undefined ) {
+ // Set the scroll offset
+ return this.each(function() {
+ win = getWindow( this );
+
+ if ( win ) {
+ win.scrollTo(
+ !i ? val : jQuery(win).scrollLeft(),
+ i ? val : jQuery(win).scrollTop()
+ );
+
+ } else {
+ this[ method ] = val;
+ }
+ });
+ } else {
+ win = getWindow( elem );
+
+ // Return the scroll offset
+ return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+ };
+});
+
+function getWindow( elem ) {
+ return ("scrollTo" in elem && elem.document) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+ var type = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function() {
+ return this[0] ?
+ jQuery.css( this[0], type, false, "padding" ) :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function( margin ) {
+ return this[0] ?
+ jQuery.css( this[0], type, false, margin ? "margin" : "border" ) :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ var elem = this[0];
+ if ( !elem ) {
+ return size == null ? null : this;
+ }
+
+ if ( jQuery.isFunction( size ) ) {
+ return this.each(function( i ) {
+ var self = jQuery( this );
+ self[ type ]( size.call( this, i, self[ type ]() ) );
+ });
+ }
+
+ return ("scrollTo" in elem && elem.document) ? // does it walk and quack like a window?
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] ||
+ elem.document.body[ "client" + name ] :
+
+ // Get document width or height
+ (elem.nodeType === 9) ? // is it a document
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ elem.documentElement["client" + name],
+ elem.body["scroll" + name], elem.documentElement["scroll" + name],
+ elem.body["offset" + name], elem.documentElement["offset" + name]
+ ) :
+
+ // Get or set width or height on the element
+ size === undefined ?
+ // Get width or height on the element
+ jQuery.css( elem, type ) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, typeof size === "string" ? size : size + "px" );
+ };
+
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+})(window);
diff --git a/lib/jquery/jquery-1.4.2.min.js b/lib/jquery/jquery-1.4.2.min.js
new file mode 100644
index 00000000..7c243080
--- /dev/null
+++ b/lib/jquery/jquery-1.4.2.min.js
@@ -0,0 +1,154 @@
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
diff --git a/lib/jquery/jquery-ui-1.7.1.custom.min.js b/lib/jquery/jquery-ui-1.7.1.custom.min.js
new file mode 100644
index 00000000..7e37b4a2
--- /dev/null
+++ b/lib/jquery/jquery-ui-1.7.1.custom.min.js
@@ -0,0 +1,77 @@
+/*
+ * jQuery UI 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
+ */ jQuery.ui||(function(c){var i=c.fn.remove,d=c.browser.mozilla&&(parseFloat(c.browser.version)<1.9);c.ui={version:"1.7.1",plugin:{add:function(k,l,n){var m=c.ui[k].prototype;for(var j in n){m.plugins[j]=m.plugins[j]||[];m.plugins[j].push([l,n[j]])}},call:function(j,l,k){var n=j.plugins[l];if(!n||!j.element[0].parentNode){return}for(var m=0;m<n.length;m++){if(j.options[n[m][0]]){n[m][1].apply(j.element,k)}}}},contains:function(k,j){return document.compareDocumentPosition?k.compareDocumentPosition(j)&16:k!==j&&k.contains(j)},hasScroll:function(m,k){if(c(m).css("overflow")=="hidden"){return false}var j=(k&&k=="left")?"scrollLeft":"scrollTop",l=false;if(m[j]>0){return true}m[j]=1;l=(m[j]>0);m[j]=0;return l},isOverAxis:function(k,j,l){return(k>j)&&(k<(j+l))},isOver:function(o,k,n,m,j,l){return c.ui.isOverAxis(o,n,j)&&c.ui.isOverAxis(k,m,l)},keyCode:{BACKSPACE:8,CAPS_LOCK:20,COMMA:188,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38}};if(d){var f=c.attr,e=c.fn.removeAttr,h="http://www.w3.org/2005/07/aaa",a=/^aria-/,b=/^wairole:/;c.attr=function(k,j,l){var m=l!==undefined;return(j=="role"?(m?f.call(this,k,j,"wairole:"+l):(f.apply(this,arguments)||"").replace(b,"")):(a.test(j)?(m?k.setAttributeNS(h,j.replace(a,"aaa:"),l):f.call(this,k,j.replace(a,"aaa:"))):f.apply(this,arguments)))};c.fn.removeAttr=function(j){return(a.test(j)?this.each(function(){this.removeAttributeNS(h,j.replace(a,""))}):e.call(this,j))}}c.fn.extend({remove:function(){c("*",this).add(this).each(function(){c(this).triggerHandler("remove")});return i.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","").unbind("selectstart.ui")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return false})},scrollParent:function(){var j;if((c.browser.msie&&(/(static|relative)/).test(this.css("position")))||(/absolute/).test(this.css("position"))){j=this.parents().filter(function(){return(/(relative|absolute|fixed)/).test(c.curCSS(this,"position",1))&&(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}else{j=this.parents().filter(function(){return(/(auto|scroll)/).test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0)}return(/fixed/).test(this.css("position"))||!j.length?c(document):j}});c.extend(c.expr[":"],{data:function(l,k,j){return !!c.data(l,j[3])},focusable:function(k){var l=k.nodeName.toLowerCase(),j=c.attr(k,"tabindex");return(/input|select|textarea|button|object/.test(l)?!k.disabled:"a"==l||"area"==l?k.href||!isNaN(j):!isNaN(j))&&!c(k)["area"==l?"parents":"closest"](":hidden").length},tabbable:function(k){var j=c.attr(k,"tabindex");return(isNaN(j)||j>=0)&&c(k).is(":focusable")}});function g(m,n,o,l){function k(q){var p=c[m][n][q]||[];return(typeof p=="string"?p.split(/,?\s+/):p)}var j=k("getter");if(l.length==1&&typeof l[0]=="string"){j=j.concat(k("getterSetter"))}return(c.inArray(o,j)!=-1)}c.widget=function(k,j){var l=k.split(".")[0];k=k.split(".")[1];c.fn[k]=function(p){var n=(typeof p=="string"),o=Array.prototype.slice.call(arguments,1);if(n&&p.substring(0,1)=="_"){return this}if(n&&g(l,k,p,o)){var m=c.data(this[0],k);return(m?m[p].apply(m,o):undefined)}return this.each(function(){var q=c.data(this,k);(!q&&!n&&c.data(this,k,new c[l][k](this,p))._init());(q&&n&&c.isFunction(q[p])&&q[p].apply(q,o))})};c[l]=c[l]||{};c[l][k]=function(o,n){var m=this;this.namespace=l;this.widgetName=k;this.widgetEventPrefix=c[l][k].eventPrefix||k;this.widgetBaseClass=l+"-"+k;this.options=c.extend({},c.widget.defaults,c[l][k].defaults,c.metadata&&c.metadata.get(o)[k],n);this.element=c(o).bind("setData."+k,function(q,p,r){if(q.target==o){return m._setData(p,r)}}).bind("getData."+k,function(q,p){if(q.target==o){return m._getData(p)}}).bind("remove",function(){return m.destroy()})};c[l][k].prototype=c.extend({},c.widget.prototype,j);c[l][k].getterSetter="option"};c.widget.prototype={_init:function(){},destroy:function(){this.element.removeData(this.widgetName).removeClass(this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").removeAttr("aria-disabled")},option:function(l,m){var k=l,j=this;if(typeof l=="string"){if(m===undefined){return this._getData(l)}k={};k[l]=m}c.each(k,function(n,o){j._setData(n,o)})},_getData:function(j){return this.options[j]},_setData:function(j,k){this.options[j]=k;if(j=="disabled"){this.element[k?"addClass":"removeClass"](this.widgetBaseClass+"-disabled "+this.namespace+"-state-disabled").attr("aria-disabled",k)}},enable:function(){this._setData("disabled",false)},disable:function(){this._setData("disabled",true)},_trigger:function(l,m,n){var p=this.options[l],j=(l==this.widgetEventPrefix?l:this.widgetEventPrefix+l);m=c.Event(m);m.type=j;if(m.originalEvent){for(var k=c.event.props.length,o;k;){o=c.event.props[--k];m[o]=m.originalEvent[o]}}this.element.trigger(m,n);return !(c.isFunction(p)&&p.call(this.element[0],m,n)===false||m.isDefaultPrevented())}};c.widget.defaults={disabled:false};c.ui.mouse={_mouseInit:function(){var j=this;this.element.bind("mousedown."+this.widgetName,function(k){return j._mouseDown(k)}).bind("click."+this.widgetName,function(k){if(j._preventClickEvent){j._preventClickEvent=false;k.stopImmediatePropagation();return false}});if(c.browser.msie){this._mouseUnselectable=this.element.attr("unselectable");this.element.attr("unselectable","on")}this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName);(c.browser.msie&&this.element.attr("unselectable",this._mouseUnselectable))},_mouseDown:function(l){l.originalEvent=l.originalEvent||{};if(l.originalEvent.mouseHandled){return}(this._mouseStarted&&this._mouseUp(l));this._mouseDownEvent=l;var k=this,m=(l.which==1),j=(typeof this.options.cancel=="string"?c(l.target).parents().add(l.target).filter(this.options.cancel).length:false);if(!m||j||!this._mouseCapture(l)){return true}this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet){this._mouseDelayTimer=setTimeout(function(){k.mouseDelayMet=true},this.options.delay)}if(this._mouseDistanceMet(l)&&this._mouseDelayMet(l)){this._mouseStarted=(this._mouseStart(l)!==false);if(!this._mouseStarted){l.preventDefault();return true}}this._mouseMoveDelegate=function(n){return k._mouseMove(n)};this._mouseUpDelegate=function(n){return k._mouseUp(n)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);(c.browser.safari||l.preventDefault());l.originalEvent.mouseHandled=true;return true},_mouseMove:function(j){if(c.browser.msie&&!j.button){return this._mouseUp(j)}if(this._mouseStarted){this._mouseDrag(j);return j.preventDefault()}if(this._mouseDistanceMet(j)&&this._mouseDelayMet(j)){this._mouseStarted=(this._mouseStart(this._mouseDownEvent,j)!==false);(this._mouseStarted?this._mouseDrag(j):this._mouseUp(j))}return !this._mouseStarted},_mouseUp:function(j){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=(j.target==this._mouseDownEvent.target);this._mouseStop(j)}return false},_mouseDistanceMet:function(j){return(Math.max(Math.abs(this._mouseDownEvent.pageX-j.pageX),Math.abs(this._mouseDownEvent.pageY-j.pageY))>=this.options.distance)},_mouseDelayMet:function(j){return this.mouseDelayMet},_mouseStart:function(j){},_mouseDrag:function(j){},_mouseStop:function(j){},_mouseCapture:function(j){return true}};c.ui.mouse.defaults={cancel:null,distance:1,delay:0}})(jQuery);;/*
+ * jQuery UI Accordion 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Accordion
+ *
+ * Depends:
+ * ui.core.js
+ */ (function(a){a.widget("ui.accordion",{_init:function(){var d=this.options,b=this;this.running=0;if(d.collapsible==a.ui.accordion.defaults.collapsible&&d.alwaysOpen!=a.ui.accordion.defaults.alwaysOpen){d.collapsible=!d.alwaysOpen}if(d.navigation){var c=this.element.find("a").filter(d.navigationFilter);if(c.length){if(c.filter(d.header).length){this.active=c}else{this.active=c.parent().parent().prev();c.addClass("ui-accordion-content-active")}}}this.element.addClass("ui-accordion ui-widget ui-helper-reset");if(this.element[0].nodeName=="UL"){this.element.children("li").addClass("ui-accordion-li-fix")}this.headers=this.element.find(d.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){a(this).removeClass("ui-state-focus")});this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");this.active=this._findActive(this.active||d.active).toggleClass("ui-state-default").toggleClass("ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top");this.active.next().addClass("ui-accordion-content-active");a("<span/>").addClass("ui-icon "+d.icons.header).prependTo(this.headers);this.active.find(".ui-icon").toggleClass(d.icons.header).toggleClass(d.icons.headerSelected);if(a.browser.msie){this.element.find("a").css("zoom","1")}this.resize();this.element.attr("role","tablist");this.headers.attr("role","tab").bind("keydown",function(e){return b._keydown(e)}).next().attr("role","tabpanel");this.headers.not(this.active||"").attr("aria-expanded","false").attr("tabIndex","-1").next().hide();if(!this.active.length){this.headers.eq(0).attr("tabIndex","0")}else{this.active.attr("aria-expanded","true").attr("tabIndex","0")}if(!a.browser.safari){this.headers.find("a").attr("tabIndex","-1")}if(d.event){this.headers.bind((d.event)+".accordion",function(e){return b._clickHandler.call(b,e,this)})}},destroy:function(){var c=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role").unbind(".accordion").removeData("accordion");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabindex");this.headers.find("a").removeAttr("tabindex");this.headers.children(".ui-icon").remove();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active");if(c.autoHeight||c.fillHeight){b.css("height","")}},_setData:function(b,c){if(b=="alwaysOpen"){b="collapsible";c=!c}a.widget.prototype._setData.apply(this,arguments)},_keydown:function(e){var g=this.options,f=a.ui.keyCode;if(g.disabled||e.altKey||e.ctrlKey){return}var d=this.headers.length;var b=this.headers.index(e.target);var c=false;switch(e.keyCode){case f.RIGHT:case f.DOWN:c=this.headers[(b+1)%d];break;case f.LEFT:case f.UP:c=this.headers[(b-1+d)%d];break;case f.SPACE:case f.ENTER:return this._clickHandler({target:e.target},e.target)}if(c){a(e.target).attr("tabIndex","-1");a(c).attr("tabIndex","0");c.focus();return false}return true},resize:function(){var e=this.options,d;if(e.fillSpace){if(a.browser.msie){var b=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}d=this.element.parent().height();if(a.browser.msie){this.element.parent().css("overflow",b)}this.headers.each(function(){d-=a(this).outerHeight()});var c=0;this.headers.next().each(function(){c=Math.max(c,a(this).innerHeight()-a(this).height())}).height(Math.max(0,d-c)).css("overflow","auto")}else{if(e.autoHeight){d=0;this.headers.next().each(function(){d=Math.max(d,a(this).outerHeight())}).height(d)}}},activate:function(b){var c=this._findActive(b)[0];this._clickHandler({target:c},c)},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===false?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,f){var d=this.options;if(d.disabled){return false}if(!b.target&&d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");var h=this.active.next(),e={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:h},c=(this.active=a([]));this._toggle(c,h,e);return false}var g=a(b.currentTarget||f);var i=g[0]==this.active[0];if(this.running||(!d.collapsible&&i)){return false}this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").find(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);this.active.next().addClass("ui-accordion-content-active");if(!i){g.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").find(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);g.next().addClass("ui-accordion-content-active")}var c=g.next(),h=this.active.next(),e={options:d,newHeader:i&&d.collapsible?a([]):g,oldHeader:this.active,newContent:i&&d.collapsible?a([]):c.find("> *"),oldContent:h.find("> *")},j=this.headers.index(this.active[0])>this.headers.index(g[0]);this.active=i?a([]):g;this._toggle(c,h,e,i,j);return false},_toggle:function(b,i,g,j,k){var d=this.options,m=this;this.toShow=b;this.toHide=i;this.data=g;var c=function(){if(!m){return}return m._completed.apply(m,arguments)};this._trigger("changestart",null,this.data);this.running=i.size()===0?b.size():i.size();if(d.animated){var f={};if(d.collapsible&&j){f={toShow:a([]),toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}else{f={toShow:b,toHide:i,complete:c,down:k,autoHeight:d.autoHeight||d.fillSpace}}if(!d.proxied){d.proxied=d.animated}if(!d.proxiedDuration){d.proxiedDuration=d.duration}d.animated=a.isFunction(d.proxied)?d.proxied(f):d.proxied;d.duration=a.isFunction(d.proxiedDuration)?d.proxiedDuration(f):d.proxiedDuration;var l=a.ui.accordion.animations,e=d.duration,h=d.animated;if(!l[h]){l[h]=function(n){this.slide(n,{easing:h,duration:e||700})}}l[h](f)}else{if(d.collapsible&&j){b.toggle()}else{i.hide();b.show()}c(true)}i.prev().attr("aria-expanded","false").attr("tabIndex","-1").blur();b.prev().attr("aria-expanded","true").attr("tabIndex","0").focus()},_completed:function(b){var c=this.options;this.running=b?0:--this.running;if(this.running){return}if(c.clearStyle){this.toShow.add(this.toHide).css({height:"",overflow:""})}this._trigger("change",null,this.data)}});a.extend(a.ui.accordion,{version:"1.7.1",defaults:{active:null,alwaysOpen:true,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()==location.href.toLowerCase()}},animations:{slide:function(j,h){j=a.extend({easing:"swing",duration:300},j,h);if(!j.toHide.size()){j.toShow.animate({height:"show"},j);return}if(!j.toShow.size()){j.toHide.animate({height:"hide"},j);return}var c=j.toShow.css("overflow"),g,d={},f={},e=["height","paddingTop","paddingBottom"],b;var i=j.toShow;b=i[0].style.width;i.width(parseInt(i.parent().width(),10)-parseInt(i.css("paddingLeft"),10)-parseInt(i.css("paddingRight"),10)-(parseInt(i.css("borderLeftWidth"),10)||0)-(parseInt(i.css("borderRightWidth"),10)||0));a.each(e,function(k,m){f[m]="hide";var l=(""+a.css(j.toShow[0],m)).match(/^([\d+-.]+)(.*)$/);d[m]={value:l[1],unit:l[2]||"px"}});j.toShow.css({height:0,overflow:"hidden"}).show();j.toHide.filter(":hidden").each(j.complete).end().filter(":visible").animate(f,{step:function(k,l){if(l.prop=="height"){g=(l.now-l.start)/(l.end-l.start)}j.toShow[0].style[l.prop]=(g*d[l.prop].value)+d[l.prop].unit},duration:j.duration,easing:j.easing,complete:function(){if(!j.autoHeight){j.toShow.css("height","")}j.toShow.css("width",b);j.toShow.css({overflow:c});j.complete()}})},bounceslide:function(b){this.slide(b,{easing:b.down?"easeOutBounce":"swing",duration:b.down?1000:200})},easeslide:function(b){this.slide(b,{easing:"easeinout",duration:700})}}})})(jQuery);;/*
+ * jQuery UI Dialog 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ * ui.core.js
+ * ui.draggable.js
+ * ui.resizable.js
+ */ (function(c){var b={dragStart:"start.draggable",drag:"drag.draggable",dragStop:"stop.draggable",maxHeight:"maxHeight.resizable",minHeight:"minHeight.resizable",maxWidth:"maxWidth.resizable",minWidth:"minWidth.resizable",resizeStart:"start.resizable",resize:"drag.resizable",resizeStop:"stop.resizable"},a="ui-dialog ui-widget ui-widget-content ui-corner-all ";c.widget("ui.dialog",{_init:function(){this.originalTitle=this.element.attr("title");var l=this,m=this.options,j=m.title||this.originalTitle||"&nbsp;",e=c.ui.dialog.getTitleId(this.element),k=(this.uiDialog=c("<div/>")).appendTo(document.body).hide().addClass(a+m.dialogClass).css({position:"absolute",overflow:"hidden",zIndex:m.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(n){(m.closeOnEscape&&n.keyCode&&n.keyCode==c.ui.keyCode.ESCAPE&&l.close(n))}).attr({role:"dialog","aria-labelledby":e}).mousedown(function(n){l.moveToTop(false,n)}),g=this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(k),f=(this.uiDialogTitlebar=c("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(k),i=c('<a href="#"/>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){i.addClass("ui-state-hover")},function(){i.removeClass("ui-state-hover")}).focus(function(){i.addClass("ui-state-focus")}).blur(function(){i.removeClass("ui-state-focus")}).mousedown(function(n){n.stopPropagation()}).click(function(n){l.close(n);return false}).appendTo(f),h=(this.uiDialogTitlebarCloseText=c("<span/>")).addClass("ui-icon ui-icon-closethick").text(m.closeText).appendTo(i),d=c("<span/>").addClass("ui-dialog-title").attr("id",e).html(j).prependTo(f);f.find("*").add(f).disableSelection();(m.draggable&&c.fn.draggable&&this._makeDraggable());(m.resizable&&c.fn.resizable&&this._makeResizable());this._createButtons(m.buttons);this._isOpen=false;(m.bgiframe&&c.fn.bgiframe&&k.bgiframe());(m.autoOpen&&this.open())},destroy:function(){(this.overlay&&this.overlay.destroy());this.uiDialog.hide();this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body");this.uiDialog.remove();(this.originalTitle&&this.element.attr("title",this.originalTitle))},close:function(e){var d=this;if(false===d._trigger("beforeclose",e)){return}(d.overlay&&d.overlay.destroy());d.uiDialog.unbind("keypress.ui-dialog");(d.options.hide?d.uiDialog.hide(d.options.hide,function(){d._trigger("close",e)}):d.uiDialog.hide()&&d._trigger("close",e));c.ui.dialog.overlay.resize();d._isOpen=false},isOpen:function(){return this._isOpen},moveToTop:function(f,e){if((this.options.modal&&!f)||(!this.options.stack&&!this.options.modal)){return this._trigger("focus",e)}if(this.options.zIndex>c.ui.dialog.maxZ){c.ui.dialog.maxZ=this.options.zIndex}(this.overlay&&this.overlay.$el.css("z-index",c.ui.dialog.overlay.maxZ=++c.ui.dialog.maxZ));var d={scrollTop:this.element.attr("scrollTop"),scrollLeft:this.element.attr("scrollLeft")};this.uiDialog.css("z-index",++c.ui.dialog.maxZ);this.element.attr(d);this._trigger("focus",e)},open:function(){if(this._isOpen){return}var e=this.options,d=this.uiDialog;this.overlay=e.modal?new c.ui.dialog.overlay(this):null;(d.next().length&&d.appendTo("body"));this._size();this._position(e.position);d.show(e.show);this.moveToTop(true);(e.modal&&d.bind("keypress.ui-dialog",function(h){if(h.keyCode!=c.ui.keyCode.TAB){return}var g=c(":tabbable",this),i=g.filter(":first")[0],f=g.filter(":last")[0];if(h.target==f&&!h.shiftKey){setTimeout(function(){i.focus()},1)}else{if(h.target==i&&h.shiftKey){setTimeout(function(){f.focus()},1)}}}));c([]).add(d.find(".ui-dialog-content :tabbable:first")).add(d.find(".ui-dialog-buttonpane :tabbable:first")).add(d).filter(":first").focus();this._trigger("open");this._isOpen=true},_createButtons:function(g){var f=this,d=false,e=c("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");this.uiDialog.find(".ui-dialog-buttonpane").remove();(typeof g=="object"&&g!==null&&c.each(g,function(){return !(d=true)}));if(d){c.each(g,function(h,i){c('<button type="button"></button>').addClass("ui-state-default ui-corner-all").text(h).click(function(){i.apply(f.element[0],arguments)}).hover(function(){c(this).addClass("ui-state-hover")},function(){c(this).removeClass("ui-state-hover")}).focus(function(){c(this).addClass("ui-state-focus")}).blur(function(){c(this).removeClass("ui-state-focus")}).appendTo(e)});e.appendTo(this.uiDialog)}},_makeDraggable:function(){var d=this,f=this.options,e;this.uiDialog.draggable({cancel:".ui-dialog-content",handle:".ui-dialog-titlebar",containment:"document",start:function(){e=f.height;c(this).height(c(this).height()).addClass("ui-dialog-dragging");(f.dragStart&&f.dragStart.apply(d.element[0],arguments))},drag:function(){(f.drag&&f.drag.apply(d.element[0],arguments))},stop:function(){c(this).removeClass("ui-dialog-dragging").height(e);(f.dragStop&&f.dragStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}})},_makeResizable:function(g){g=(g===undefined?this.options.resizable:g);var d=this,f=this.options,e=typeof g=="string"?g:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",alsoResize:this.element,maxWidth:f.maxWidth,maxHeight:f.maxHeight,minWidth:f.minWidth,minHeight:f.minHeight,start:function(){c(this).addClass("ui-dialog-resizing");(f.resizeStart&&f.resizeStart.apply(d.element[0],arguments))},resize:function(){(f.resize&&f.resize.apply(d.element[0],arguments))},handles:e,stop:function(){c(this).removeClass("ui-dialog-resizing");f.height=c(this).height();f.width=c(this).width();(f.resizeStop&&f.resizeStop.apply(d.element[0],arguments));c.ui.dialog.overlay.resize()}}).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_position:function(i){var e=c(window),f=c(document),g=f.scrollTop(),d=f.scrollLeft(),h=g;if(c.inArray(i,["center","top","right","bottom","left"])>=0){i=[i=="right"||i=="left"?i:"center",i=="top"||i=="bottom"?i:"middle"]}if(i.constructor!=Array){i=["center","middle"]}if(i[0].constructor==Number){d+=i[0]}else{switch(i[0]){case"left":d+=0;break;case"right":d+=e.width()-this.uiDialog.outerWidth();break;default:case"center":d+=(e.width()-this.uiDialog.outerWidth())/2}}if(i[1].constructor==Number){g+=i[1]}else{switch(i[1]){case"top":g+=0;break;case"bottom":g+=e.height()-this.uiDialog.outerHeight();break;default:case"middle":g+=(e.height()-this.uiDialog.outerHeight())/2}}g=Math.max(g,h);this.uiDialog.css({top:g,left:d})},_setData:function(e,f){(b[e]&&this.uiDialog.data(b[e],f));switch(e){case"buttons":this._createButtons(f);break;case"closeText":this.uiDialogTitlebarCloseText.text(f);break;case"dialogClass":this.uiDialog.removeClass(this.options.dialogClass).addClass(a+f);break;case"draggable":(f?this._makeDraggable():this.uiDialog.draggable("destroy"));break;case"height":this.uiDialog.height(f);break;case"position":this._position(f);break;case"resizable":var d=this.uiDialog,g=this.uiDialog.is(":data(resizable)");(g&&!f&&d.resizable("destroy"));(g&&typeof f=="string"&&d.resizable("option","handles",f));(g||this._makeResizable(f));break;case"title":c(".ui-dialog-title",this.uiDialogTitlebar).html(f||"&nbsp;");break;case"width":this.uiDialog.width(f);break}c.widget.prototype._setData.apply(this,arguments)},_size:function(){var e=this.options;this.element.css({height:0,minHeight:0,width:"auto"});var d=this.uiDialog.css({height:"auto",width:e.width}).height();this.element.css({minHeight:Math.max(e.minHeight-d,0),height:e.height=="auto"?"auto":Math.max(e.height-d,0)})}});c.extend(c.ui.dialog,{version:"1.7.1",defaults:{autoOpen:true,bgiframe:false,buttons:{},closeOnEscape:true,closeText:"close",dialogClass:"",draggable:true,hide:null,height:"auto",maxHeight:false,maxWidth:false,minHeight:150,minWidth:150,modal:false,position:"center",resizable:true,show:null,stack:true,title:"",width:300,zIndex:1000},getter:"isOpen",uuid:0,maxZ:0,getTitleId:function(d){return"ui-dialog-title-"+(d.attr("id")||++this.uuid)},overlay:function(d){this.$el=c.ui.dialog.overlay.create(d)}});c.extend(c.ui.dialog.overlay,{instances:[],maxZ:0,events:c.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(d){return d+".dialog-overlay"}).join(" "),create:function(e){if(this.instances.length===0){setTimeout(function(){c(document).bind(c.ui.dialog.overlay.events,function(f){var g=c(f.target).parents(".ui-dialog").css("zIndex")||0;return(g>c.ui.dialog.overlay.maxZ)})},1);c(document).bind("keydown.dialog-overlay",function(f){(e.options.closeOnEscape&&f.keyCode&&f.keyCode==c.ui.keyCode.ESCAPE&&e.close(f))});c(window).bind("resize.dialog-overlay",c.ui.dialog.overlay.resize)}var d=c("<div></div>").appendTo(document.body).addClass("ui-widget-overlay").css({width:this.width(),height:this.height()});(e.options.bgiframe&&c.fn.bgiframe&&d.bgiframe());this.instances.push(d);return d},destroy:function(d){this.instances.splice(c.inArray(this.instances,d),1);if(this.instances.length===0){c([document,window]).unbind(".dialog-overlay")}d.remove()},height:function(){if(c.browser.msie&&c.browser.version<7){var e=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight);var d=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);if(e<d){return c(window).height()+"px"}else{return e+"px"}}else{return c(document).height()+"px"}},width:function(){if(c.browser.msie&&c.browser.version<7){var d=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth);var e=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);if(d<e){return c(window).width()+"px"}else{return d+"px"}}else{return c(document).width()+"px"}},resize:function(){var d=c([]);c.each(c.ui.dialog.overlay.instances,function(){d=d.add(this)});d.css({width:0,height:0}).css({width:c.ui.dialog.overlay.width(),height:c.ui.dialog.overlay.height()})}});c.extend(c.ui.dialog.overlay.prototype,{destroy:function(){c.ui.dialog.overlay.destroy(this.$el)}})})(jQuery);;/*
+ * jQuery UI Slider 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Slider
+ *
+ * Depends:
+ * ui.core.js
+ */ (function(a){a.widget("ui.slider",a.extend({},a.ui.mouse,{_init:function(){var b=this,c=this.options;this._keySliding=false;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");this.range=a([]);if(c.range){if(c.range===true){this.range=a("<div></div>");if(!c.values){c.values=[this._valueMin(),this._valueMin()]}if(c.values.length&&c.values.length!=2){c.values=[c.values[0],c.values[0]]}}else{this.range=a("<div></div>")}this.range.appendTo(this.element).addClass("ui-slider-range");if(c.range=="min"||c.range=="max"){this.range.addClass("ui-slider-range-"+c.range)}this.range.addClass("ui-widget-header")}if(a(".ui-slider-handle",this.element).length==0){a('<a href="#"></a>').appendTo(this.element).addClass("ui-slider-handle")}if(c.values&&c.values.length){while(a(".ui-slider-handle",this.element).length<c.values.length){a('<a href="#"></a>').appendTo(this.element).addClass("ui-slider-handle")}}this.handles=a(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(d){d.preventDefault()}).hover(function(){a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){a(".ui-slider .ui-state-focus").removeClass("ui-state-focus");a(this).addClass("ui-state-focus")}).blur(function(){a(this).removeClass("ui-state-focus")});this.handles.each(function(d){a(this).data("index.ui-slider-handle",d)});this.handles.keydown(function(i){var f=true;var e=a(this).data("index.ui-slider-handle");if(b.options.disabled){return}switch(i.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:f=false;if(!b._keySliding){b._keySliding=true;a(this).addClass("ui-state-active");b._start(i,e)}break}var g,d,h=b._step();if(b.options.values&&b.options.values.length){g=d=b.values(e)}else{g=d=b.value()}switch(i.keyCode){case a.ui.keyCode.HOME:d=b._valueMin();break;case a.ui.keyCode.END:d=b._valueMax();break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(g==b._valueMax()){return}d=g+h;break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(g==b._valueMin()){return}d=g-h;break}b._slide(i,e,d);return f}).keyup(function(e){var d=a(this).data("index.ui-slider-handle");if(b._keySliding){b._stop(e,d);b._change(e,d);b._keySliding=false;a(this).removeClass("ui-state-active")}});this._refreshValue()},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");this._mouseDestroy()},_mouseCapture:function(d){var e=this.options;if(e.disabled){return false}this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();var h={x:d.pageX,y:d.pageY};var j=this._normValueFromMouse(h);var c=this._valueMax()-this._valueMin()+1,f;var k=this,i;this.handles.each(function(l){var m=Math.abs(j-k.values(l));if(c>m){c=m;f=a(this);i=l}});if(e.range==true&&this.values(1)==e.min){f=a(this.handles[++i])}this._start(d,i);k._handleIndex=i;f.addClass("ui-state-active").focus();var g=f.offset();var b=!a(d.target).parents().andSelf().is(".ui-slider-handle");this._clickOffset=b?{left:0,top:0}:{left:d.pageX-g.left-(f.width()/2),top:d.pageY-g.top-(f.height()/2)-(parseInt(f.css("borderTopWidth"),10)||0)-(parseInt(f.css("borderBottomWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0)};j=this._normValueFromMouse(h);this._slide(d,i,j);return true},_mouseStart:function(b){return true},_mouseDrag:function(d){var b={x:d.pageX,y:d.pageY};var c=this._normValueFromMouse(b);this._slide(d,this._handleIndex,c);return false},_mouseStop:function(b){this.handles.removeClass("ui-state-active");this._stop(b,this._handleIndex);this._change(b,this._handleIndex);this._handleIndex=null;this._clickOffset=null;return false},_detectOrientation:function(){this.orientation=this.options.orientation=="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(d){var c,h;if("horizontal"==this.orientation){c=this.elementSize.width;h=d.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{c=this.elementSize.height;h=d.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}var f=(h/c);if(f>1){f=1}if(f<0){f=0}if("vertical"==this.orientation){f=1-f}var e=this._valueMax()-this._valueMin(),i=f*e,b=i%this.options.step,g=this._valueMin()+i-b;if(b>(this.options.step/2)){g+=this.options.step}return parseFloat(g.toFixed(5))},_start:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("start",d,b)},_slide:function(f,e,d){var g=this.handles[e];if(this.options.values&&this.options.values.length){var b=this.values(e?0:1);if((e==0&&d>=b)||(e==1&&d<=b)){d=b}if(d!=this.values(e)){var c=this.values();c[e]=d;var h=this._trigger("slide",f,{handle:this.handles[e],value:d,values:c});var b=this.values(e?0:1);if(h!==false){this.values(e,d,(f.type=="mousedown"&&this.options.animate),true)}}}else{if(d!=this.value()){var h=this._trigger("slide",f,{handle:this.handles[e],value:d});if(h!==false){this._setData("value",d,(f.type=="mousedown"&&this.options.animate))}}}},_stop:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("stop",d,b)},_change:function(d,c){var b={handle:this.handles[c],value:this.value()};if(this.options.values&&this.options.values.length){b.value=this.values(c);b.values=this.values()}this._trigger("change",d,b)},value:function(b){if(arguments.length){this._setData("value",b);this._change(null,0)}return this._value()},values:function(b,e,c,d){if(arguments.length>1){this.options.values[b]=e;this._refreshValue(c);if(!d){this._change(null,b)}}if(arguments.length){if(this.options.values&&this.options.values.length){return this._values(b)}else{return this.value()}}else{return this._values()}},_setData:function(b,d,c){a.widget.prototype._setData.apply(this,arguments);switch(b){case"orientation":this._detectOrientation();this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue(c);break;case"value":this._refreshValue(c);break}},_step:function(){var b=this.options.step;return b},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_values:function(b){if(arguments.length){var c=this.options.values[b];if(c<this._valueMin()){c=this._valueMin()}if(c>this._valueMax()){c=this._valueMax()}return c}else{return this.options.values}},_valueMin:function(){var b=this.options.min;return b},_valueMax:function(){var b=this.options.max;return b},_refreshValue:function(c){var f=this.options.range,d=this.options,l=this;if(this.options.values&&this.options.values.length){var i,h;this.handles.each(function(p,n){var o=(l.values(p)-l._valueMin())/(l._valueMax()-l._valueMin())*100;var m={};m[l.orientation=="horizontal"?"left":"bottom"]=o+"%";a(this).stop(1,1)[c?"animate":"css"](m,d.animate);if(l.options.range===true){if(l.orientation=="horizontal"){(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({left:o+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({width:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}else{(p==0)&&l.range.stop(1,1)[c?"animate":"css"]({bottom:(o)+"%"},d.animate);(p==1)&&l.range[c?"animate":"css"]({height:(o-lastValPercent)+"%"},{queue:false,duration:d.animate})}}lastValPercent=o})}else{var j=this.value(),g=this._valueMin(),k=this._valueMax(),e=k!=g?(j-g)/(k-g)*100:0;var b={};b[l.orientation=="horizontal"?"left":"bottom"]=e+"%";this.handle.stop(1,1)[c?"animate":"css"](b,d.animate);(f=="min")&&(this.orientation=="horizontal")&&this.range.stop(1,1)[c?"animate":"css"]({width:e+"%"},d.animate);(f=="max")&&(this.orientation=="horizontal")&&this.range[c?"animate":"css"]({width:(100-e)+"%"},{queue:false,duration:d.animate});(f=="min")&&(this.orientation=="vertical")&&this.range.stop(1,1)[c?"animate":"css"]({height:e+"%"},d.animate);(f=="max")&&(this.orientation=="vertical")&&this.range[c?"animate":"css"]({height:(100-e)+"%"},{queue:false,duration:d.animate})}}}));a.extend(a.ui.slider,{getter:"value values",version:"1.7.1",eventPrefix:"slide",defaults:{animate:false,delay:0,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null}})})(jQuery);;/*
+ * jQuery UI Tabs 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Tabs
+ *
+ * Depends:
+ * ui.core.js
+ */ (function(a){a.widget("ui.tabs",{_init:function(){if(this.options.deselectable!==undefined){this.options.collapsible=this.options.deselectable}this._tabify(true)},_setData:function(b,c){if(b=="selected"){if(this.options.collapsible&&c==this.options.selected){return}this.select(c)}else{this.options[b]=c;if(b=="deselectable"){this.options.collapsible=c}this._tabify()}},_tabId:function(b){return b.title&&b.title.replace(/\s/g,"_").replace(/[^A-Za-z0-9\-_:\.]/g,"")||this.options.idPrefix+a.data(b)},_sanitizeSelector:function(b){return b.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+a.data(this.list[0]));return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(c,b){return{tab:c,panel:b,index:this.anchors.index(c)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(n){this.list=this.element.children("ul:first");this.lis=a("li:has(a[href])",this.list);this.anchors=this.lis.map(function(){return a("a",this)[0]});this.panels=a([]);var p=this,d=this.options;var c=/^#.+/;this.anchors.each(function(r,o){var q=a(o).attr("href");var s=q.split("#")[0],u;if(s&&(s===location.toString().split("#")[0]||(u=a("base")[0])&&s===u.href)){q=o.hash;o.href=q}if(c.test(q)){p.panels=p.panels.add(p._sanitizeSelector(q))}else{if(q!="#"){a.data(o,"href.tabs",q);a.data(o,"load.tabs",q.replace(/#.*$/,""));var w=p._tabId(o);o.href="#"+w;var v=a("#"+w);if(!v.length){v=a(d.panelTemplate).attr("id",w).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(p.panels[r-1]||p.list);v.data("destroy.tabs",true)}p.panels=p.panels.add(v)}else{d.disabled.push(r)}}});if(n){this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all");this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.lis.addClass("ui-state-default ui-corner-top");this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom");if(d.selected===undefined){if(location.hash){this.anchors.each(function(q,o){if(o.hash==location.hash){d.selected=q;return false}})}if(typeof d.selected!="number"&&d.cookie){d.selected=parseInt(p._cookie(),10)}if(typeof d.selected!="number"&&this.lis.filter(".ui-tabs-selected").length){d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}d.selected=d.selected||0}else{if(d.selected===null){d.selected=-1}}d.selected=((d.selected>=0&&this.anchors[d.selected])||d.selected<0)?d.selected:0;d.disabled=a.unique(d.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(q,o){return p.lis.index(q)}))).sort();if(a.inArray(d.selected,d.disabled)!=-1){d.disabled.splice(a.inArray(d.selected,d.disabled),1)}this.panels.addClass("ui-tabs-hide");this.lis.removeClass("ui-tabs-selected ui-state-active");if(d.selected>=0&&this.anchors.length){this.panels.eq(d.selected).removeClass("ui-tabs-hide");this.lis.eq(d.selected).addClass("ui-tabs-selected ui-state-active");p.element.queue("tabs",function(){p._trigger("show",null,p._ui(p.anchors[d.selected],p.panels[d.selected]))});this.load(d.selected)}a(window).bind("unload",function(){p.lis.add(p.anchors).unbind(".tabs");p.lis=p.anchors=p.panels=null})}else{d.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))}this.element[d.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible");if(d.cookie){this._cookie(d.selected,d.cookie)}for(var g=0,m;(m=this.lis[g]);g++){a(m)[a.inArray(g,d.disabled)!=-1&&!a(m).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled")}if(d.cache===false){this.anchors.removeData("cache.tabs")}this.lis.add(this.anchors).unbind(".tabs");if(d.event!="mouseover"){var f=function(o,i){if(i.is(":not(.ui-state-disabled)")){i.addClass("ui-state-"+o)}};var j=function(o,i){i.removeClass("ui-state-"+o)};this.lis.bind("mouseover.tabs",function(){f("hover",a(this))});this.lis.bind("mouseout.tabs",function(){j("hover",a(this))});this.anchors.bind("focus.tabs",function(){f("focus",a(this).closest("li"))});this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var b,h;if(d.fx){if(a.isArray(d.fx)){b=d.fx[0];h=d.fx[1]}else{b=h=d.fx}}function e(i,o){i.css({display:""});if(a.browser.msie&&o.opacity){i[0].style.removeAttribute("filter")}}var k=h?function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.hide().removeClass("ui-tabs-hide").animate(h,h.duration||"normal",function(){e(o,h);p._trigger("show",null,p._ui(i,o[0]))})}:function(i,o){a(i).closest("li").removeClass("ui-state-default").addClass("ui-tabs-selected ui-state-active");o.removeClass("ui-tabs-hide");p._trigger("show",null,p._ui(i,o[0]))};var l=b?function(o,i){i.animate(b,b.duration||"normal",function(){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");e(i,b);p.element.dequeue("tabs")})}:function(o,i,q){p.lis.removeClass("ui-tabs-selected ui-state-active").addClass("ui-state-default");i.addClass("ui-tabs-hide");p.element.dequeue("tabs")};this.anchors.bind(d.event+".tabs",function(){var o=this,r=a(this).closest("li"),i=p.panels.filter(":not(.ui-tabs-hide)"),q=a(p._sanitizeSelector(this.hash));if((r.hasClass("ui-tabs-selected")&&!d.collapsible)||r.hasClass("ui-state-disabled")||r.hasClass("ui-state-processing")||p._trigger("select",null,p._ui(this,q[0]))===false){this.blur();return false}d.selected=p.anchors.index(this);p.abort();if(d.collapsible){if(r.hasClass("ui-tabs-selected")){d.selected=-1;if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){l(o,i)}).dequeue("tabs");this.blur();return false}else{if(!i.length){if(d.cookie){p._cookie(d.selected,d.cookie)}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this));this.blur();return false}}}if(d.cookie){p._cookie(d.selected,d.cookie)}if(q.length){if(i.length){p.element.queue("tabs",function(){l(o,i)})}p.element.queue("tabs",function(){k(o,q)});p.load(p.anchors.index(this))}else{throw"jQuery UI Tabs: Mismatching fragment identifier."}if(a.browser.msie){this.blur()}});this.anchors.bind("click.tabs",function(){return false})},destroy:function(){var b=this.options;this.abort();this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs");this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all");this.anchors.each(function(){var c=a.data(this,"href.tabs");if(c){this.href=c}var d=a(this).unbind(".tabs");a.each(["href","load","cache"],function(e,f){d.removeData(f+".tabs")})});this.lis.unbind(".tabs").add(this.panels).each(function(){if(a.data(this,"destroy.tabs")){a(this).remove()}else{a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}});if(b.cookie){this._cookie(null,b.cookie)}},add:function(e,d,c){if(c===undefined){c=this.anchors.length}var b=this,g=this.options,i=a(g.tabTemplate.replace(/#\{href\}/g,e).replace(/#\{label\}/g,d)),h=!e.indexOf("#")?e.replace("#",""):this._tabId(a("a",i)[0]);i.addClass("ui-state-default ui-corner-top").data("destroy.tabs",true);var f=a("#"+h);if(!f.length){f=a(g.panelTemplate).attr("id",h).data("destroy.tabs",true)}f.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide");if(c>=this.lis.length){i.appendTo(this.list);f.appendTo(this.list[0].parentNode)}else{i.insertBefore(this.lis[c]);f.insertBefore(this.panels[c])}g.disabled=a.map(g.disabled,function(k,j){return k>=c?++k:k});this._tabify();if(this.anchors.length==1){i.addClass("ui-tabs-selected ui-state-active");f.removeClass("ui-tabs-hide");this.element.queue("tabs",function(){b._trigger("show",null,b._ui(b.anchors[0],b.panels[0]))});this.load(0)}this._trigger("add",null,this._ui(this.anchors[c],this.panels[c]))},remove:function(b){var d=this.options,e=this.lis.eq(b).remove(),c=this.panels.eq(b).remove();if(e.hasClass("ui-tabs-selected")&&this.anchors.length>1){this.select(b+(b+1<this.anchors.length?1:-1))}d.disabled=a.map(a.grep(d.disabled,function(g,f){return g!=b}),function(g,f){return g>=b?--g:g});this._tabify();this._trigger("remove",null,this._ui(e.find("a")[0],c[0]))},enable:function(b){var c=this.options;if(a.inArray(b,c.disabled)==-1){return}this.lis.eq(b).removeClass("ui-state-disabled");c.disabled=a.grep(c.disabled,function(e,d){return e!=b});this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]))},disable:function(c){var b=this,d=this.options;if(c!=d.selected){this.lis.eq(c).addClass("ui-state-disabled");d.disabled.push(c);d.disabled.sort();this._trigger("disable",null,this._ui(this.anchors[c],this.panels[c]))}},select:function(b){if(typeof b=="string"){b=this.anchors.index(this.anchors.filter("[href$="+b+"]"))}else{if(b===null){b=-1}}if(b==-1&&this.options.collapsible){b=this.options.selected}this.anchors.eq(b).trigger(this.options.event+".tabs")},load:function(e){var c=this,g=this.options,b=this.anchors.eq(e)[0],d=a.data(b,"load.tabs");this.abort();if(!d||this.element.queue("tabs").length!==0&&a.data(b,"cache.tabs")){this.element.dequeue("tabs");return}this.lis.eq(e).addClass("ui-state-processing");if(g.spinner){var f=a("span",b);f.data("label.tabs",f.html()).html(g.spinner)}this.xhr=a.ajax(a.extend({},g.ajaxOptions,{url:d,success:function(i,h){a(c._sanitizeSelector(b.hash)).html(i);c._cleanup();if(g.cache){a.data(b,"cache.tabs",true)}c._trigger("load",null,c._ui(c.anchors[e],c.panels[e]));try{g.ajaxOptions.success(i,h)}catch(j){}c.element.dequeue("tabs")}}))},abort:function(){this.element.queue([]);this.panels.stop(false,true);if(this.xhr){this.xhr.abort();delete this.xhr}this._cleanup()},url:function(c,b){this.anchors.eq(c).removeData("cache.tabs").data("load.tabs",b)},length:function(){return this.anchors.length}});a.extend(a.ui.tabs,{version:"1.7.1",getter:"length",defaults:{ajaxOptions:null,cache:false,cookie:null,collapsible:false,disabled:[],event:"click",fx:null,idPrefix:"ui-tabs-",panelTemplate:"<div></div>",spinner:"<em>Loading&#8230;</em>",tabTemplate:'<li><a href="#{href}"><span>#{label}</span></a></li>'}});a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(d,f){var b=this,g=this.options;var c=b._rotate||(b._rotate=function(h){clearTimeout(b.rotation);b.rotation=setTimeout(function(){var i=g.selected;b.select(++i<b.anchors.length?i:0)},d);if(h){h.stopPropagation()}});var e=b._unrotate||(b._unrotate=!f?function(h){if(h.clientX){b.rotate(null)}}:function(h){t=g.selected;c()});if(d){this.element.bind("tabsshow",c);this.anchors.bind(g.event+".tabs",e);c()}else{clearTimeout(b.rotation);this.element.unbind("tabsshow",c);this.anchors.unbind(g.event+".tabs",e);delete this._rotate;delete this._unrotate}}})})(jQuery);;/*
+ * jQuery UI Datepicker 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Datepicker
+ *
+ * Depends:
+ * ui.core.js
+ */ (function($){$.extend($.ui,{datepicker:{version:"1.7.1"}});var PROP_NAME="datepicker";function Datepicker(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._datepickerShowing=false;this._inDialog=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass="ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],dateFormat:"mm/dd/yy",firstDay:0,isRTL:false};this._defaults={showOn:"focus",showAnim:"show",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,showMonthAfterYear:false,yearRange:"-10:+10",showOtherMonths:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"normal",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false};$.extend(this._defaults,this.regional[""]);this.dpDiv=$('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",log:function(){if(this.debug){console.log.apply("",arguments)}},setDefaults:function(settings){extendRemove(this._defaults,settings||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase();var inline=(nodeName=="div"||nodeName=="span");if(!target.id){target.id="dp"+(++this.uuid)}var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{});if(nodeName=="input"){this._connectDatepicker(target,inst)}else{if(inline){this._inlineDatepicker(target,inst)}}},_newInst:function(target,inline){var id=target[0].id.replace(/([:\[\]\.])/g,"\\\\$1");return{id:id,input:target,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:inline,dpDiv:(!inline?this.dpDiv:$('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(target,inst){var input=$(target);inst.trigger=$([]);if(input.hasClass(this.markerClassName)){return}var appendText=this._get(inst,"appendText");var isRTL=this._get(inst,"isRTL");if(appendText){input[isRTL?"before":"after"]('<span class="'+this._appendClass+'">'+appendText+"</span>")}var showOn=this._get(inst,"showOn");if(showOn=="focus"||showOn=="both"){input.focus(this._showDatepicker)}if(showOn=="button"||showOn=="both"){var buttonText=this._get(inst,"buttonText");var buttonImage=this._get(inst,"buttonImage");inst.trigger=$(this._get(inst,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:buttonImage,alt:buttonText,title:buttonText}):$('<button type="button"></button>').addClass(this._triggerClass).html(buttonImage==""?buttonText:$("<img/>").attr({src:buttonImage,alt:buttonText,title:buttonText})));input[isRTL?"before":"after"](inst.trigger);inst.trigger.click(function(){if($.datepicker._datepickerShowing&&$.datepicker._lastInput==target){$.datepicker._hideDatepicker()}else{$.datepicker._showDatepicker(target)}return false})}input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst)},_inlineDatepicker:function(target,inst){var divSpan=$(target);if(divSpan.hasClass(this.markerClassName)){return}divSpan.addClass(this.markerClassName).append(inst.dpDiv).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst);this._setDate(inst,this._getDefaultDate(inst));this._updateDatepicker(inst);this._updateAlternate(inst)},_dialogDatepicker:function(input,dateText,onSelect,settings,pos){var inst=this._dialogInst;if(!inst){var id="dp"+(++this.uuid);this._dialogInput=$('<input type="text" id="'+id+'" size="1" style="position: absolute; top: -100px;"/>');this._dialogInput.keydown(this._doKeyDown);$("body").append(this._dialogInput);inst=this._dialogInst=this._newInst(this._dialogInput,false);inst.settings={};$.data(this._dialogInput[0],PROP_NAME,inst)}extendRemove(inst.settings,settings||{});this._dialogInput.val(dateText);this._pos=(pos?(pos.length?pos:[pos.pageX,pos.pageY]):null);if(!this._pos){var browserWidth=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;var browserHeight=window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight;var scrollX=document.documentElement.scrollLeft||document.body.scrollLeft;var scrollY=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(browserWidth/2)-100+scrollX,(browserHeight/2)-150+scrollY]}this._dialogInput.css("left",this._pos[0]+"px").css("top",this._pos[1]+"px");inst.settings.onSelect=onSelect;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if($.blockUI){$.blockUI(this.dpDiv)}$.data(this._dialogInput[0],PROP_NAME,inst);return this},_destroyDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();$.removeData(target,PROP_NAME);if(nodeName=="input"){inst.trigger.remove();$target.siblings("."+this._appendClass).remove().end().removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress)}else{if(nodeName=="div"||nodeName=="span"){$target.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=false;inst.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().removeClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)})},_disableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=true;inst.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().addClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)});this._disabledInputs[this._disabledInputs.length]=target},_isDisabledDatepicker:function(target){if(!target){return false}for(var i=0;i<this._disabledInputs.length;i++){if(this._disabledInputs[i]==target){return true}}return false},_getInst:function(target){try{return $.data(target,PROP_NAME)}catch(err){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(target,name,value){var settings=name||{};if(typeof name=="string"){settings={};settings[name]=value}var inst=this._getInst(target);if(inst){if(this._curInst==inst){this._hideDatepicker(null)}extendRemove(inst.settings,settings);var date=new Date();extendRemove(inst,{rangeStart:null,endDay:null,endMonth:null,endYear:null,selectedDay:date.getDate(),selectedMonth:date.getMonth(),selectedYear:date.getFullYear(),currentDay:date.getDate(),currentMonth:date.getMonth(),currentYear:date.getFullYear(),drawMonth:date.getMonth(),drawYear:date.getFullYear()});this._updateDatepicker(inst)}},_changeDatepicker:function(target,name,value){this._optionDatepicker(target,name,value)},_refreshDatepicker:function(target){var inst=this._getInst(target);if(inst){this._updateDatepicker(inst)}},_setDateDatepicker:function(target,date,endDate){var inst=this._getInst(target);if(inst){this._setDate(inst,date,endDate);this._updateDatepicker(inst);this._updateAlternate(inst)}},_getDateDatepicker:function(target){var inst=this._getInst(target);if(inst&&!inst.inline){this._setDateFromField(inst)}return(inst?this._getDate(inst):null)},_doKeyDown:function(event){var inst=$.datepicker._getInst(event.target);var handled=true;var isRTL=inst.dpDiv.is(".ui-datepicker-rtl");inst._keyEvent=true;if($.datepicker._datepickerShowing){switch(event.keyCode){case 9:$.datepicker._hideDatepicker(null,"");break;case 13:var sel=$("td."+$.datepicker._dayOverClass+", td."+$.datepicker._currentClass,inst.dpDiv);if(sel[0]){$.datepicker._selectDay(event.target,inst.selectedMonth,inst.selectedYear,sel[0])}else{$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"))}return false;break;case 27:$.datepicker._hideDatepicker(null,$.datepicker._get(inst,"duration"));break;case 33:$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M");break;case 34:$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M");break;case 35:if(event.ctrlKey||event.metaKey){$.datepicker._clearDate(event.target)}handled=event.ctrlKey||event.metaKey;break;case 36:if(event.ctrlKey||event.metaKey){$.datepicker._gotoToday(event.target)}handled=event.ctrlKey||event.metaKey;break;case 37:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?+1:-1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M")}break;case 38:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,-7,"D")}handled=event.ctrlKey||event.metaKey;break;case 39:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?-1:+1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M")}break;case 40:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,+7,"D")}handled=event.ctrlKey||event.metaKey;break;default:handled=false}}else{if(event.keyCode==36&&event.ctrlKey){$.datepicker._showDatepicker(this)}else{handled=false}}if(handled){event.preventDefault();event.stopPropagation()}},_doKeyPress:function(event){var inst=$.datepicker._getInst(event.target);if($.datepicker._get(inst,"constrainInput")){var chars=$.datepicker._possibleChars($.datepicker._get(inst,"dateFormat"));var chr=String.fromCharCode(event.charCode==undefined?event.keyCode:event.charCode);return event.ctrlKey||(chr<" "||!chars||chars.indexOf(chr)>-1)}},_showDatepicker:function(input){input=input.target||input;if(input.nodeName.toLowerCase()!="input"){input=$("input",input.parentNode)[0]}if($.datepicker._isDisabledDatepicker(input)||$.datepicker._lastInput==input){return}var inst=$.datepicker._getInst(input);var beforeShow=$.datepicker._get(inst,"beforeShow");extendRemove(inst.settings,(beforeShow?beforeShow.apply(input,[input,inst]):{}));$.datepicker._hideDatepicker(null,"");$.datepicker._lastInput=input;$.datepicker._setDateFromField(inst);if($.datepicker._inDialog){input.value=""}if(!$.datepicker._pos){$.datepicker._pos=$.datepicker._findPos(input);$.datepicker._pos[1]+=input.offsetHeight}var isFixed=false;$(input).parents().each(function(){isFixed|=$(this).css("position")=="fixed";return !isFixed});if(isFixed&&$.browser.opera){$.datepicker._pos[0]-=document.documentElement.scrollLeft;$.datepicker._pos[1]-=document.documentElement.scrollTop}var offset={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null;inst.rangeStart=null;inst.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});$.datepicker._updateDatepicker(inst);offset=$.datepicker._checkOffset(inst,offset,isFixed);inst.dpDiv.css({position:($.datepicker._inDialog&&$.blockUI?"static":(isFixed?"fixed":"absolute")),display:"none",left:offset.left+"px",top:offset.top+"px"});if(!inst.inline){var showAnim=$.datepicker._get(inst,"showAnim")||"show";var duration=$.datepicker._get(inst,"duration");var postProcess=function(){$.datepicker._datepickerShowing=true;if($.browser.msie&&parseInt($.browser.version,10)<7){$("iframe.ui-datepicker-cover").css({width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4})}};if($.effects&&$.effects[showAnim]){inst.dpDiv.show(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[showAnim](duration,postProcess)}if(duration==""){postProcess()}if(inst.input[0].type!="hidden"){inst.input[0].focus()}$.datepicker._curInst=inst}},_updateDatepicker:function(inst){var dims={width:inst.dpDiv.width()+4,height:inst.dpDiv.height()+4};var self=this;inst.dpDiv.empty().append(this._generateHTML(inst)).find("iframe.ui-datepicker-cover").css({width:dims.width,height:dims.height}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){$(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).removeClass("ui-datepicker-next-hover")}}).bind("mouseover",function(){if(!self._isDisabledDatepicker(inst.inline?inst.dpDiv.parent()[0]:inst.input[0])){$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");$(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).addClass("ui-datepicker-next-hover")}}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();var numMonths=this._getNumberOfMonths(inst);var cols=numMonths[1];var width=17;if(cols>1){inst.dpDiv.addClass("ui-datepicker-multi-"+cols).css("width",(width*cols)+"em")}else{inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("")}inst.dpDiv[(numMonths[0]!=1||numMonths[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");inst.dpDiv[(this._get(inst,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(inst.input&&inst.input[0].type!="hidden"&&inst==$.datepicker._curInst){$(inst.input[0]).focus()}},_checkOffset:function(inst,offset,isFixed){var dpWidth=inst.dpDiv.outerWidth();var dpHeight=inst.dpDiv.outerHeight();var inputWidth=inst.input?inst.input.outerWidth():0;var inputHeight=inst.input?inst.input.outerHeight():0;var viewWidth=(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)+$(document).scrollLeft();var viewHeight=(window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight)+$(document).scrollTop();offset.left-=(this._get(inst,"isRTL")?(dpWidth-inputWidth):0);offset.left-=(isFixed&&offset.left==inst.input.offset().left)?$(document).scrollLeft():0;offset.top-=(isFixed&&offset.top==(inst.input.offset().top+inputHeight))?$(document).scrollTop():0;offset.left-=(offset.left+dpWidth>viewWidth&&viewWidth>dpWidth)?Math.abs(offset.left+dpWidth-viewWidth):0;offset.top-=(offset.top+dpHeight>viewHeight&&viewHeight>dpHeight)?Math.abs(offset.top+dpHeight+inputHeight*2-viewHeight):0;return offset},_findPos:function(obj){while(obj&&(obj.type=="hidden"||obj.nodeType!=1)){obj=obj.nextSibling}var position=$(obj).offset();return[position.left,position.top]},_hideDatepicker:function(input,duration){var inst=this._curInst;if(!inst||(input&&inst!=$.data(input,PROP_NAME))){return}if(inst.stayOpen){this._selectDate("#"+inst.id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear))}inst.stayOpen=false;if(this._datepickerShowing){duration=(duration!=null?duration:this._get(inst,"duration"));var showAnim=this._get(inst,"showAnim");var postProcess=function(){$.datepicker._tidyDialog(inst)};if(duration!=""&&$.effects&&$.effects[showAnim]){inst.dpDiv.hide(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[(duration==""?"hide":(showAnim=="slideDown"?"slideUp":(showAnim=="fadeIn"?"fadeOut":"hide")))](duration,postProcess)}if(duration==""){this._tidyDialog(inst)}var onClose=this._get(inst,"onClose");if(onClose){onClose.apply((inst.input?inst.input[0]:null),[(inst.input?inst.input.val():""),inst])}this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if($.blockUI){$.unblockUI();$("body").append(this.dpDiv)}}this._inDialog=false}this._curInst=null},_tidyDialog:function(inst){inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(event){if(!$.datepicker._curInst){return}var $target=$(event.target);if(($target.parents("#"+$.datepicker._mainDivId).length==0)&&!$target.hasClass($.datepicker.markerClassName)&&!$target.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&!($.datepicker._inDialog&&$.blockUI)){$.datepicker._hideDatepicker(null,"")}},_adjustDate:function(id,offset,period){var target=$(id);var inst=this._getInst(target[0]);if(this._isDisabledDatepicker(target[0])){return}this._adjustInstDate(inst,offset+(period=="M"?this._get(inst,"showCurrentAtPos"):0),period);this._updateDatepicker(inst)},_gotoToday:function(id){var target=$(id);var inst=this._getInst(target[0]);if(this._get(inst,"gotoCurrent")&&inst.currentDay){inst.selectedDay=inst.currentDay;inst.drawMonth=inst.selectedMonth=inst.currentMonth;inst.drawYear=inst.selectedYear=inst.currentYear}else{var date=new Date();inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear()}this._notifyChange(inst);this._adjustDate(target)},_selectMonthYear:function(id,select,period){var target=$(id);var inst=this._getInst(target[0]);inst._selectingMonthYear=false;inst["selected"+(period=="M"?"Month":"Year")]=inst["draw"+(period=="M"?"Month":"Year")]=parseInt(select.options[select.selectedIndex].value,10);this._notifyChange(inst);this._adjustDate(target)},_clickMonthYear:function(id){var target=$(id);var inst=this._getInst(target[0]);if(inst.input&&inst._selectingMonthYear&&!$.browser.msie){inst.input[0].focus()}inst._selectingMonthYear=!inst._selectingMonthYear},_selectDay:function(id,month,year,td){var target=$(id);if($(td).hasClass(this._unselectableClass)||this._isDisabledDatepicker(target[0])){return}var inst=this._getInst(target[0]);inst.selectedDay=inst.currentDay=$("a",td).html();inst.selectedMonth=inst.currentMonth=month;inst.selectedYear=inst.currentYear=year;if(inst.stayOpen){inst.endDay=inst.endMonth=inst.endYear=null}this._selectDate(id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear));if(inst.stayOpen){inst.rangeStart=this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay));this._updateDatepicker(inst)}},_clearDate:function(id){var target=$(id);var inst=this._getInst(target[0]);inst.stayOpen=false;inst.endDay=inst.endMonth=inst.endYear=inst.rangeStart=null;this._selectDate(target,"")},_selectDate:function(id,dateStr){var target=$(id);var inst=this._getInst(target[0]);dateStr=(dateStr!=null?dateStr:this._formatDate(inst));if(inst.input){inst.input.val(dateStr)}this._updateAlternate(inst);var onSelect=this._get(inst,"onSelect");if(onSelect){onSelect.apply((inst.input?inst.input[0]:null),[dateStr,inst])}else{if(inst.input){inst.input.trigger("change")}}if(inst.inline){this._updateDatepicker(inst)}else{if(!inst.stayOpen){this._hideDatepicker(null,this._get(inst,"duration"));this._lastInput=inst.input[0];if(typeof(inst.input[0])!="object"){inst.input[0].focus()}this._lastInput=null}}},_updateAlternate:function(inst){var altField=this._get(inst,"altField");if(altField){var altFormat=this._get(inst,"altFormat")||this._get(inst,"dateFormat");var date=this._getDate(inst);dateStr=this.formatDate(altFormat,date,this._getFormatConfig(inst));$(altField).each(function(){$(this).val(dateStr)})}},noWeekends:function(date){var day=date.getDay();return[(day>0&&day<6),""]},iso8601Week:function(date){var checkDate=new Date(date.getFullYear(),date.getMonth(),date.getDate());var firstMon=new Date(checkDate.getFullYear(),1-1,4);var firstDay=firstMon.getDay()||7;firstMon.setDate(firstMon.getDate()+1-firstDay);if(firstDay<4&&checkDate<firstMon){checkDate.setDate(checkDate.getDate()-3);return $.datepicker.iso8601Week(checkDate)}else{if(checkDate>new Date(checkDate.getFullYear(),12-1,28)){firstDay=new Date(checkDate.getFullYear()+1,1-1,4).getDay()||7;if(firstDay>4&&(checkDate.getDay()||7)<firstDay-3){return 1}}}return Math.floor(((checkDate-firstMon)/86400000)/7)+1},parseDate:function(format,value,settings){if(format==null||value==null){throw"Invalid arguments"}value=(typeof value=="object"?value.toString():value+"");if(value==""){return null}var shortYearCutoff=(settings?settings.shortYearCutoff:null)||this._defaults.shortYearCutoff;var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var year=-1;var month=-1;var day=-1;var doy=-1;var literal=false;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var getNumber=function(match){lookAhead(match);var origSize=(match=="@"?14:(match=="y"?4:(match=="o"?3:2)));var size=origSize;var num=0;while(size>0&&iValue<value.length&&value.charAt(iValue)>="0"&&value.charAt(iValue)<="9"){num=num*10+parseInt(value.charAt(iValue++),10);size--}if(size==origSize){throw"Missing number at position "+iValue}return num};var getName=function(match,shortNames,longNames){var names=(lookAhead(match)?longNames:shortNames);var size=0;for(var j=0;j<names.length;j++){size=Math.max(size,names[j].length)}var name="";var iInit=iValue;while(size>0&&iValue<value.length){name+=value.charAt(iValue++);for(var i=0;i<names.length;i++){if(name==names[i]){return i+1}}size--}throw"Unknown name at position "+iInit};var checkLiteral=function(){if(value.charAt(iValue)!=format.charAt(iFormat)){throw"Unexpected literal at position "+iValue}iValue++};var iValue=0;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{checkLiteral()}}else{switch(format.charAt(iFormat)){case"d":day=getNumber("d");break;case"D":getName("D",dayNamesShort,dayNames);break;case"o":doy=getNumber("o");break;case"m":month=getNumber("m");break;case"M":month=getName("M",monthNamesShort,monthNames);break;case"y":year=getNumber("y");break;case"@":var date=new Date(getNumber("@"));year=date.getFullYear();month=date.getMonth()+1;day=date.getDate();break;case"'":if(lookAhead("'")){checkLiteral()}else{literal=true}break;default:checkLiteral()}}}if(year==-1){year=new Date().getFullYear()}else{if(year<100){year+=new Date().getFullYear()-new Date().getFullYear()%100+(year<=shortYearCutoff?0:-100)}}if(doy>-1){month=1;day=doy;do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}var date=this._daylightSavingAdjust(new Date(year,month-1,day));if(date.getFullYear()!=year||date.getMonth()+1!=month||date.getDate()!=day){throw"Invalid date"}return date},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TIMESTAMP:"@",W3C:"yy-mm-dd",formatDate:function(format,date,settings){if(!date){return""}var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var formatNumber=function(match,value,len){var num=""+value;if(lookAhead(match)){while(num.length<len){num="0"+num}}return num};var formatName=function(match,value,shortNames,longNames){return(lookAhead(match)?longNames[value]:shortNames[value])};var output="";var literal=false;if(date){for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{output+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":output+=formatNumber("d",date.getDate(),2);break;case"D":output+=formatName("D",date.getDay(),dayNamesShort,dayNames);break;case"o":var doy=date.getDate();for(var m=date.getMonth()-1;m>=0;m--){doy+=this._getDaysInMonth(date.getFullYear(),m)}output+=formatNumber("o",doy,3);break;case"m":output+=formatNumber("m",date.getMonth()+1,2);break;case"M":output+=formatName("M",date.getMonth(),monthNamesShort,monthNames);break;case"y":output+=(lookAhead("y")?date.getFullYear():(date.getYear()%100<10?"0":"")+date.getYear()%100);break;case"@":output+=date.getTime();break;case"'":if(lookAhead("'")){output+="'"}else{literal=true}break;default:output+=format.charAt(iFormat)}}}}return output},_possibleChars:function(format){var chars="";var literal=false;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{chars+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":case"m":case"y":case"@":chars+="0123456789";break;case"D":case"M":return null;case"'":if(lookAhead("'")){chars+="'"}else{literal=true}break;default:chars+=format.charAt(iFormat)}}}return chars},_get:function(inst,name){return inst.settings[name]!==undefined?inst.settings[name]:this._defaults[name]},_setDateFromField:function(inst){var dateFormat=this._get(inst,"dateFormat");var dates=inst.input?inst.input.val():null;inst.endDay=inst.endMonth=inst.endYear=null;var date=defaultDate=this._getDefaultDate(inst);var settings=this._getFormatConfig(inst);try{date=this.parseDate(dateFormat,dates,settings)||defaultDate}catch(event){this.log(event);date=defaultDate}inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();inst.currentDay=(dates?date.getDate():0);inst.currentMonth=(dates?date.getMonth():0);inst.currentYear=(dates?date.getFullYear():0);this._adjustInstDate(inst)},_getDefaultDate:function(inst){var date=this._determineDate(this._get(inst,"defaultDate"),new Date());var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);return date},_determineDate:function(date,defaultDate){var offsetNumeric=function(offset){var date=new Date();date.setDate(date.getDate()+offset);return date};var offsetString=function(offset,getDaysInMonth){var date=new Date();var year=date.getFullYear();var month=date.getMonth();var day=date.getDate();var pattern=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;var matches=pattern.exec(offset);while(matches){switch(matches[2]||"d"){case"d":case"D":day+=parseInt(matches[1],10);break;case"w":case"W":day+=parseInt(matches[1],10)*7;break;case"m":case"M":month+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break;case"y":case"Y":year+=parseInt(matches[1],10);day=Math.min(day,getDaysInMonth(year,month));break}matches=pattern.exec(offset)}return new Date(year,month,day)};date=(date==null?defaultDate:(typeof date=="string"?offsetString(date,this._getDaysInMonth):(typeof date=="number"?(isNaN(date)?defaultDate:offsetNumeric(date)):date)));date=(date&&date.toString()=="Invalid Date"?defaultDate:date);if(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0)}return this._daylightSavingAdjust(date)},_daylightSavingAdjust:function(date){if(!date){return null}date.setHours(date.getHours()>12?date.getHours()+2:0);return date},_setDate:function(inst,date,endDate){var clear=!(date);var origMonth=inst.selectedMonth;var origYear=inst.selectedYear;date=this._determineDate(date,new Date());inst.selectedDay=inst.currentDay=date.getDate();inst.drawMonth=inst.selectedMonth=inst.currentMonth=date.getMonth();inst.drawYear=inst.selectedYear=inst.currentYear=date.getFullYear();if(origMonth!=inst.selectedMonth||origYear!=inst.selectedYear){this._notifyChange(inst)}this._adjustInstDate(inst);if(inst.input){inst.input.val(clear?"":this._formatDate(inst))}},_getDate:function(inst){var startDate=(!inst.currentYear||(inst.input&&inst.input.val()=="")?null:this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return startDate},_generateHTML:function(inst){var today=new Date();today=this._daylightSavingAdjust(new Date(today.getFullYear(),today.getMonth(),today.getDate()));var isRTL=this._get(inst,"isRTL");var showButtonPanel=this._get(inst,"showButtonPanel");var hideIfNoPrevNext=this._get(inst,"hideIfNoPrevNext");var navigationAsDateFormat=this._get(inst,"navigationAsDateFormat");var numMonths=this._getNumberOfMonths(inst);var showCurrentAtPos=this._get(inst,"showCurrentAtPos");var stepMonths=this._get(inst,"stepMonths");var stepBigMonths=this._get(inst,"stepBigMonths");var isMultiMonth=(numMonths[0]!=1||numMonths[1]!=1);var currentDate=this._daylightSavingAdjust((!inst.currentDay?new Date(9999,9,9):new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");var drawMonth=inst.drawMonth-showCurrentAtPos;var drawYear=inst.drawYear;if(drawMonth<0){drawMonth+=12;drawYear--}if(maxDate){var maxDraw=this._daylightSavingAdjust(new Date(maxDate.getFullYear(),maxDate.getMonth()-numMonths[1]+1,maxDate.getDate()));maxDraw=(minDate&&maxDraw<minDate?minDate:maxDraw);while(this._daylightSavingAdjust(new Date(drawYear,drawMonth,1))>maxDraw){drawMonth--;if(drawMonth<0){drawMonth=11;drawYear--}}}inst.drawMonth=drawMonth;inst.drawYear=drawYear;var prevText=this._get(inst,"prevText");prevText=(!navigationAsDateFormat?prevText:this.formatDate(prevText,this._daylightSavingAdjust(new Date(drawYear,drawMonth-stepMonths,1)),this._getFormatConfig(inst)));var prev=(this._canAdjustMonth(inst,-1,drawYear,drawMonth)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', -"+stepMonths+", 'M');\" title=\""+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>"));var nextText=this._get(inst,"nextText");nextText=(!navigationAsDateFormat?nextText:this.formatDate(nextText,this._daylightSavingAdjust(new Date(drawYear,drawMonth+stepMonths,1)),this._getFormatConfig(inst)));var next=(this._canAdjustMonth(inst,+1,drawYear,drawMonth)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery.datepicker._adjustDate(\'#'+inst.id+"', +"+stepMonths+", 'M');\" title=\""+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>"));var currentText=this._get(inst,"currentText");var gotoDate=(this._get(inst,"gotoCurrent")&&inst.currentDay?currentDate:today);currentText=(!navigationAsDateFormat?currentText:this.formatDate(currentText,gotoDate,this._getFormatConfig(inst)));var controls=(!inst.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery.datepicker._hideDatepicker();">'+this._get(inst,"closeText")+"</button>":"");var buttonPanel=(showButtonPanel)?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(isRTL?controls:"")+(this._isInRange(inst,gotoDate)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery.datepicker._gotoToday(\'#'+inst.id+"');\">"+currentText+"</button>":"")+(isRTL?"":controls)+"</div>":"";var firstDay=parseInt(this._get(inst,"firstDay"),10);firstDay=(isNaN(firstDay)?0:firstDay);var dayNames=this._get(inst,"dayNames");var dayNamesShort=this._get(inst,"dayNamesShort");var dayNamesMin=this._get(inst,"dayNamesMin");var monthNames=this._get(inst,"monthNames");var monthNamesShort=this._get(inst,"monthNamesShort");var beforeShowDay=this._get(inst,"beforeShowDay");var showOtherMonths=this._get(inst,"showOtherMonths");var calculateWeek=this._get(inst,"calculateWeek")||this.iso8601Week;var endDate=inst.endDay?this._daylightSavingAdjust(new Date(inst.endYear,inst.endMonth,inst.endDay)):currentDate;var defaultDate=this._getDefaultDate(inst);var html="";for(var row=0;row<numMonths[0];row++){var group="";for(var col=0;col<numMonths[1];col++){var selectedDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,inst.selectedDay));var cornerClass=" ui-corner-all";var calender="";if(isMultiMonth){calender+='<div class="ui-datepicker-group ui-datepicker-group-';switch(col){case 0:calender+="first";cornerClass=" ui-corner-"+(isRTL?"right":"left");break;case numMonths[1]-1:calender+="last";cornerClass=" ui-corner-"+(isRTL?"left":"right");break;default:calender+="middle";cornerClass="";break}calender+='">'}calender+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+cornerClass+'">'+(/all|left/.test(cornerClass)&&row==0?(isRTL?next:prev):"")+(/all|right/.test(cornerClass)&&row==0?(isRTL?prev:next):"")+this._generateMonthYearHeader(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,row>0||col>0,monthNames,monthNamesShort)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var thead="";for(var dow=0;dow<7;dow++){var day=(dow+firstDay)%7;thead+="<th"+((dow+firstDay+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+dayNames[day]+'">'+dayNamesMin[day]+"</span></th>"}calender+=thead+"</tr></thead><tbody>";var daysInMonth=this._getDaysInMonth(drawYear,drawMonth);if(drawYear==inst.selectedYear&&drawMonth==inst.selectedMonth){inst.selectedDay=Math.min(inst.selectedDay,daysInMonth)}var leadDays=(this._getFirstDayOfMonth(drawYear,drawMonth)-firstDay+7)%7;var numRows=(isMultiMonth?6:Math.ceil((leadDays+daysInMonth)/7));var printDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,1-leadDays));for(var dRow=0;dRow<numRows;dRow++){calender+="<tr>";var tbody="";for(var dow=0;dow<7;dow++){var daySettings=(beforeShowDay?beforeShowDay.apply((inst.input?inst.input[0]:null),[printDate]):[true,""]);var otherMonth=(printDate.getMonth()!=drawMonth);var unselectable=otherMonth||!daySettings[0]||(minDate&&printDate<minDate)||(maxDate&&printDate>maxDate);tbody+='<td class="'+((dow+firstDay+6)%7>=5?" ui-datepicker-week-end":"")+(otherMonth?" ui-datepicker-other-month":"")+((printDate.getTime()==selectedDate.getTime()&&drawMonth==inst.selectedMonth&&inst._keyEvent)||(defaultDate.getTime()==printDate.getTime()&&defaultDate.getTime()==selectedDate.getTime())?" "+this._dayOverClass:"")+(unselectable?" "+this._unselectableClass+" ui-state-disabled":"")+(otherMonth&&!showOtherMonths?"":" "+daySettings[1]+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" "+this._currentClass:"")+(printDate.getTime()==today.getTime()?" ui-datepicker-today":""))+'"'+((!otherMonth||showOtherMonths)&&daySettings[2]?' title="'+daySettings[2]+'"':"")+(unselectable?"":" onclick=\"DP_jQuery.datepicker._selectDay('#"+inst.id+"',"+drawMonth+","+drawYear+', this);return false;"')+">"+(otherMonth?(showOtherMonths?printDate.getDate():"&#xa0;"):(unselectable?'<span class="ui-state-default">'+printDate.getDate()+"</span>":'<a class="ui-state-default'+(printDate.getTime()==today.getTime()?" ui-state-highlight":"")+(printDate.getTime()>=currentDate.getTime()&&printDate.getTime()<=endDate.getTime()?" ui-state-active":"")+'" href="#">'+printDate.getDate()+"</a>"))+"</td>";printDate.setDate(printDate.getDate()+1);printDate=this._daylightSavingAdjust(printDate)}calender+=tbody+"</tr>"}drawMonth++;if(drawMonth>11){drawMonth=0;drawYear++}calender+="</tbody></table>"+(isMultiMonth?"</div>"+((numMonths[0]>0&&col==numMonths[1]-1)?'<div class="ui-datepicker-row-break"></div>':""):"");group+=calender}html+=group}html+=buttonPanel+($.browser.msie&&parseInt($.browser.version,10)<7&&!inst.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,secondary,monthNames,monthNamesShort){minDate=(inst.rangeStart&&minDate&&selectedDate<minDate?selectedDate:minDate);var changeMonth=this._get(inst,"changeMonth");var changeYear=this._get(inst,"changeYear");var showMonthAfterYear=this._get(inst,"showMonthAfterYear");var html='<div class="ui-datepicker-title">';var monthHtml="";if(secondary||!changeMonth){monthHtml+='<span class="ui-datepicker-month">'+monthNames[drawMonth]+"</span> "}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='<select class="ui-datepicker-month" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'M');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(var month=0;month<12;month++){if((!inMinYear||month>=minDate.getMonth())&&(!inMaxYear||month<=maxDate.getMonth())){monthHtml+='<option value="'+month+'"'+(month==drawMonth?' selected="selected"':"")+">"+monthNamesShort[month]+"</option>"}}monthHtml+="</select>"}if(!showMonthAfterYear){html+=monthHtml+((secondary||changeMonth||changeYear)&&(!(changeMonth&&changeYear))?"&#xa0;":"")}if(secondary||!changeYear){html+='<span class="ui-datepicker-year">'+drawYear+"</span>"}else{var years=this._get(inst,"yearRange").split(":");var year=0;var endYear=0;if(years.length!=2){year=drawYear-10;endYear=drawYear+10}else{if(years[0].charAt(0)=="+"||years[0].charAt(0)=="-"){year=drawYear+parseInt(years[0],10);endYear=drawYear+parseInt(years[1],10)}else{year=parseInt(years[0],10);endYear=parseInt(years[1],10)}}year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='<select class="ui-datepicker-year" onchange="DP_jQuery.datepicker._selectMonthYear(\'#'+inst.id+"', this, 'Y');\" onclick=\"DP_jQuery.datepicker._clickMonthYear('#"+inst.id+"');\">";for(;year<=endYear;year++){html+='<option value="'+year+'"'+(year==drawYear?' selected="selected"':"")+">"+year+"</option>"}html+="</select>"}if(showMonthAfterYear){html+=(secondary||changeMonth||changeYear?"&#xa0;":"")+monthHtml}html+="</div>";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._daylightSavingAdjust(new Date(year,month,day));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax,checkRange){var date=this._determineDate(this._get(inst,minMax+"Date"),null);return(!checkRange||!inst.rangeStart?date:(!date||inst.rangeStart>date?inst.rangeStart:date))},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var newMinDate=(!inst.rangeStart?null:this._daylightSavingAdjust(new Date(inst.selectedYear,inst.selectedMonth,inst.selectedDay)));newMinDate=(newMinDate&&inst.rangeStart<newMinDate?inst.rangeStart:newMinDate);var minDate=newMinDate||this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");return((!minDate||date>=minDate)&&(!maxDate||date<=maxDate))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.7.1";window.DP_jQuery=$})(jQuery);;/*
+ * jQuery UI Progressbar 1.7.1
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Progressbar
+ *
+ * Depends:
+ * ui.core.js
+ */ (function(a){a.widget("ui.progressbar",{_init:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this._valueMin(),"aria-valuemax":this._valueMax(),"aria-valuenow":this._value()});this.valueDiv=a('<div class="ui-progressbar-value ui-widget-header ui-corner-left"></div>').appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();a.widget.prototype.destroy.apply(this,arguments)},value:function(b){arguments.length&&this._setData("value",b);return this._value()},_setData:function(b,c){switch(b){case"value":this.options.value=c;this._refreshValue();this._trigger("change",null,{});break}a.widget.prototype._setData.apply(this,arguments)},_value:function(){var b=this.options.value;if(b<this._valueMin()){b=this._valueMin()}if(b>this._valueMax()){b=this._valueMax()}return b},_valueMin:function(){var b=0;return b},_valueMax:function(){var b=100;return b},_refreshValue:function(){var b=this.value();this.valueDiv[b==this._valueMax()?"addClass":"removeClass"]("ui-corner-right");this.valueDiv.width(b+"%");this.element.attr("aria-valuenow",b)}});a.extend(a.ui.progressbar,{version:"1.7.1",defaults:{value:0}})})(jQuery);; \ No newline at end of file
diff --git a/lib/jsl/jsl b/lib/jsl/jsl
new file mode 100755
index 00000000..97d17734
--- /dev/null
+++ b/lib/jsl/jsl
Binary files differ
diff --git a/lib/jsl/jsl.default.conf b/lib/jsl/jsl.default.conf
new file mode 100755
index 00000000..53041db8
--- /dev/null
+++ b/lib/jsl/jsl.default.conf
@@ -0,0 +1,128 @@
+#
+# Configuration File for JavaScript Lint 0.3.0
+# Developed by Matthias Miller (http://www.JavaScriptLint.com)
+#
+# This configuration file can be used to lint a collection of scripts, or to enable
+# or disable warnings for scripts that are linted via the command line.
+#
+
+### Warnings
+# Enable or disable warnings based on requirements.
+# Use "+WarningName" to display or "-WarningName" to suppress.
+#
+-no_return_value # function {0} does not always return a value
++duplicate_formal # duplicate formal argument {0}
+-equal_as_assign # test for equality (==) mistyped as assignment (=)?{0}
++var_hides_arg # variable {0} hides argument
++redeclared_var # redeclaration of {0} {1}
+-anon_no_return_value # anonymous function does not always return a value
++missing_semicolon # missing semicolon
++meaningless_block # meaningless block; curly braces have no impact
++comma_separated_stmts # multiple statements separated by commas (use semicolons?)
++unreachable_code # unreachable code
+-missing_break # missing break statement
++missing_break_for_last_case # missing break statement for last case in switch
++comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
+-inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
++useless_void # use of the void type may be unnecessary (void is always undefined)
++multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
++use_of_label # use of label
+-block_without_braces # block statement without curly braces
++leading_decimal_point # leading decimal point may indicate a number or an object member
++trailing_decimal_point # trailing decimal point may indicate a number or an object member
++octal_number # leading zeros make an octal number
++nested_comment # nested comment
+-misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma
++ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement
++empty_statement # empty statement or extra semicolon
+-missing_option_explicit # the "option explicit" control comment is missing
++partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag
++dup_option_explicit # duplicate "option explicit" control comment
++useless_assign # useless assignment
++ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity
++ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent)
+-missing_default_case # missing default case in switch statement
++duplicate_case_in_switch # duplicate case in switch statements
++default_not_at_end # the default case is not at the end of the switch statement
++legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax
++jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax
++useless_comparison # useless comparison; comparing identical expressions
++with_statement # with statement hides undeclared variables; use temporary variable instead
++trailing_comma_in_array # extra comma is not recommended in array initializers
++assign_to_function_call # assignment to a function call
++parseint_missing_radix # parseInt missing radix parameter
+
+
+### Output format
+# Customize the format of the error message.
+# __FILE__ indicates current file path
+# __FILENAME__ indicates current file name
+# __LINE__ indicates current line
+# __ERROR__ indicates error message
+#
+# Visual Studio syntax (default):
++output-format __FILE__(__LINE__): __ERROR__
+# Alternative syntax:
+#+output-format __FILE__:__LINE__: __ERROR__
+
+
+### Context
+# Show the in-line position of the error.
+# Use "+context" to display or "-context" to suppress.
+#
++context
+
+
+### Semicolons
+# By default, assignments of an anonymous function to a variable or
+# property (such as a function prototype) must be followed by a semicolon.
+#
++lambda_assign_requires_semicolon
+
+
+### Control Comments
+# Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for
+# the /*@keyword@*/ control comments and JScript conditional comments. (The latter is
+# enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason,
+# although legacy control comments are enabled by default for backward compatibility.
+#
++legacy_control_comments
+
+
+### JScript Function Extensions
+# JScript allows member functions to be defined like this:
+# function MyObj() { /*constructor*/ }
+# function MyObj.prototype.go() { /*member function*/ }
+#
+# It also allows events to be attached like this:
+# function window::onload() { /*init page*/ }
+#
+# This is a Microsoft-only JavaScript extension. Enable this setting to allow them.
+#
+-jscript_function_extensions
+
+
+### Defining identifiers
+# By default, "option explicit" is enabled on a per-file basis.
+# To enable this for all files, use "+always_use_option_explicit"
+-always_use_option_explicit
+
+# Define certain identifiers of which the lint is not aware.
+# (Use this in conjunction with the "undeclared identifier" warning.)
+#
+# Common uses for webpages might be:
+#+define window
+#+define document
+
+
+### Files
+# Specify which files to lint
+# Use "+recurse" to enable recursion (disabled by default).
+# To add a set of files, use "+process FileName", "+process Folder\Path\*.js",
+# or "+process Folder\Path\*.htm".
+#
++process src/*.js
++process src/scenario/*.js
++process test/*.js
++process test/scenario/*.js
+
diff --git a/lib/jstestdriver/JsTestDriver.jar b/lib/jstestdriver/JsTestDriver.jar
new file mode 100644
index 00000000..00482eda
--- /dev/null
+++ b/lib/jstestdriver/JsTestDriver.jar
Binary files differ
diff --git a/lib/nodeserver/server.js b/lib/nodeserver/server.js
new file mode 100644
index 00000000..13eb2826
--- /dev/null
+++ b/lib/nodeserver/server.js
@@ -0,0 +1,22 @@
+var sys = require('sys'),
+ http = require('http'),
+ fs = require('fs');
+
+http.createServer(function (req, res) {
+ res.writeHead(200, {});
+ sys.p('GET ' + req.url);
+ var file = fs.createReadStream('.' + req.url);
+ file.addListener('data', bind(res, res.write));
+ file.addListener('error', function( error ){
+ sys.p(error);
+ res.end();
+ });
+ file.addListener('close', bind(res, res.end));
+}).listen(8000);
+sys.puts('Server running at http://127.0.0.1:8000/');
+
+function bind(_this, _fn) {
+ return function(){
+ return _fn.apply(_this, arguments);
+ };
+}
diff --git a/lib/swfobject/swfobject.js b/lib/swfobject/swfobject.js
new file mode 100644
index 00000000..839d82f3
--- /dev/null
+++ b/lib/swfobject/swfobject.js
@@ -0,0 +1,4 @@
+/* SWFObject v2.2 beta1 <http://code.google.com/p/swfobject/>
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file
diff --git a/lib/underscore/underscore-min.js b/lib/underscore/underscore-min.js
new file mode 100644
index 00000000..9492ae91
--- /dev/null
+++ b/lib/underscore/underscore-min.js
@@ -0,0 +1,17 @@
+(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.8";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isNumber(a.length))for(var e=0,f=a.length;e<f;e++)c.call(d,a[e],
+e,a);else{var g=b.keys(a);f=g.length;for(e=0;e<f;e++)c.call(d,a[g[e]],g[e],a)}}catch(h){if(h!=m)throw h;}return a};b.map=function(a,c,d){if(a&&b.isFunction(a.map))return a.map(c,d);var e=[];b.each(a,function(f,g,h){e.push(c.call(d,f,g,h))});return e};b.reduce=function(a,c,d,e){if(a&&b.isFunction(a.reduce))return a.reduce(b.bind(d,e),c);b.each(a,function(f,g,h){c=d.call(e,c,f,g,h)});return c};b.reduceRight=function(a,c,d,e){if(a&&b.isFunction(a.reduceRight))return a.reduceRight(b.bind(d,e),c);var f=
+b.clone(b.toArray(a)).reverse();b.each(f,function(g,h){c=d.call(e,c,g,h,a)});return c};b.detect=function(a,c,d){var e;b.each(a,function(f,g,h){if(c.call(d,f,g,h)){e=f;b.breakLoop()}});return e};b.select=function(a,c,d){if(a&&b.isFunction(a.filter))return a.filter(c,d);var e=[];b.each(a,function(f,g,h){c.call(d,f,g,h)&&e.push(f)});return e};b.reject=function(a,c,d){var e=[];b.each(a,function(f,g,h){!c.call(d,f,g,h)&&e.push(f)});return e};b.all=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.every))return a.every(c,
+d);var e=true;b.each(a,function(f,g,h){(e=e&&c.call(d,f,g,h))||b.breakLoop()});return e};b.any=function(a,c,d){c=c||b.identity;if(a&&b.isFunction(a.some))return a.some(c,d);var e=false;b.each(a,function(f,g,h){if(e=c.call(d,f,g,h))b.breakLoop()});return e};b.include=function(a,c){if(a&&b.isFunction(a.indexOf))return b.indexOf(a,c)!=-1;var d=false;b.each(a,function(e){if(d=e===c)b.breakLoop()});return d};b.invoke=function(a,c){var d=b.rest(arguments,2);return b.map(a,function(e){return(c?e[c]:e).apply(e,
+d)})};b.pluck=function(a,c){return b.map(a,function(d){return d[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g>=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;g<e.computed&&(e={value:f,computed:g})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,
+function(e,f,g){return{value:e,criteria:c.call(d,e,f,g)}}).sort(function(e,f){e=e.criteria;f=f.criteria;return e<f?-1:e>f?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?(e=g+1):(f=g)}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return a;if(b.isArguments(a))return k.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=function(a,c,d){return c&&!d?k.call(a,
+0,c):a[0]};b.rest=function(a,c,d){return k.call(a,b.isUndefined(c)||d?1:c)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.select(a,function(c){return!!c})};b.flatten=function(a){return b.reduce(a,[],function(c,d){if(b.isArray(d))return c.concat(b.flatten(d));c.push(d);return c})};b.without=function(a){var c=b.rest(arguments);return b.select(a,function(d){return!b.include(c,d)})};b.uniq=function(a,c){return b.reduce(a,[],function(d,e,f){if(0==f||(c===true?b.last(d)!=e:!b.include(d,
+e)))d.push(e);return d})};b.intersect=function(a){var c=b.rest(arguments);return b.select(b.uniq(a),function(d){return b.all(c,function(e){return b.indexOf(e,d)>=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e<c;e++)d[e]=b.pluck(a,String(e));return d};b.indexOf=function(a,c){if(a.indexOf)return a.indexOf(c);for(var d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,c){if(a.lastIndexOf)return a.lastIndexOf(c);for(var d=
+a.length;d--;)if(a[d]===c)return d;return-1};b.range=function(a,c,d){var e=b.toArray(arguments),f=e.length<=1;a=f?0:e[0];c=f?e[0]:e[1];d=e[2]||1;e=Math.ceil((c-a)/d);if(e<=0)return[];e=new Array(e);f=a;for(var g=0;;f+=d){if((d>0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)});
+return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length);
+var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false;
+if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length==
+0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!a.concat&&!a.substr&&!a.apply&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return a===+a||p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};
+b.isRegExp=function(a){return!!(a&&a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.templateSettings={start:"<%",end:"%>",interpolate:/<%=(.+?)%>/g};b.template=function(a,c){var d=b.templateSettings;
+a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").replace(new RegExp("'(?=[^"+d.end[0]+"]*"+d.end+")","g"),"\t").split("'").join("\\'").split("\t").join("'").replace(d.interpolate,"',$1,'").split(d.start).join("');").split(d.end).join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;
+b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments);o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=
+function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})();
diff --git a/lib/underscore/underscore.js b/lib/underscore/underscore.js
new file mode 100644
index 00000000..faadaeb8
--- /dev/null
+++ b/lib/underscore/underscore.js
@@ -0,0 +1,646 @@
+// Underscore.js
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the terms of the MIT license.
+// Portions of Underscore are inspired by or borrowed from Prototype.js,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+
+(function() {
+
+ // ------------------------- Baseline setup ---------------------------------
+
+ // Establish the root object, "window" in the browser, or "global" on the server.
+ var root = this;
+
+ // Save the previous value of the "_" variable.
+ var previousUnderscore = root._;
+
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+ var wrapper = function(obj) { this._wrapped = obj; };
+
+ // Establish the object that gets thrown to break out of a loop iteration.
+ var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__';
+
+ // Create a safe reference to the Underscore object for reference below.
+ var _ = root._ = function(obj) { return new wrapper(obj); };
+
+ // Export the Underscore object for CommonJS.
+ if (typeof exports !== 'undefined') exports._ = _;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var slice = Array.prototype.slice,
+ unshift = Array.prototype.unshift,
+ toString = Object.prototype.toString,
+ hasOwnProperty = Object.prototype.hasOwnProperty,
+ propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
+
+ // Current version.
+ _.VERSION = '0.5.8';
+
+ // ------------------------ Collection Functions: ---------------------------
+
+ // The cornerstone, an each implementation.
+ // Handles objects implementing forEach, arrays, and raw objects.
+ _.each = function(obj, iterator, context) {
+ var index = 0;
+ try {
+ if (obj.forEach) {
+ obj.forEach(iterator, context);
+ } else if (_.isNumber(obj.length)) {
+ for (var i=0, l=obj.length; i<l; i++) iterator.call(context, obj[i], i, obj);
+ } else {
+ var keys = _.keys(obj), l = keys.length;
+ for (var i=0; i<l; i++) iterator.call(context, obj[keys[i]], keys[i], obj);
+ }
+ } catch(e) {
+ if (e != breaker) throw e;
+ }
+ return obj;
+ };
+
+ // Return the results of applying the iterator to each element. Use JavaScript
+ // 1.6's version of map, if possible.
+ _.map = function(obj, iterator, context) {
+ if (obj && _.isFunction(obj.map)) return obj.map(iterator, context);
+ var results = [];
+ _.each(obj, function(value, index, list) {
+ results.push(iterator.call(context, value, index, list));
+ });
+ return results;
+ };
+
+ // Reduce builds up a single result from a list of values. Also known as
+ // inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible.
+ _.reduce = function(obj, memo, iterator, context) {
+ if (obj && _.isFunction(obj.reduce)) return obj.reduce(_.bind(iterator, context), memo);
+ _.each(obj, function(value, index, list) {
+ memo = iterator.call(context, memo, value, index, list);
+ });
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as foldr. Uses
+ // JavaScript 1.8's version of reduceRight, if available.
+ _.reduceRight = function(obj, memo, iterator, context) {
+ if (obj && _.isFunction(obj.reduceRight)) return obj.reduceRight(_.bind(iterator, context), memo);
+ var reversed = _.clone(_.toArray(obj)).reverse();
+ _.each(reversed, function(value, index) {
+ memo = iterator.call(context, memo, value, index, obj);
+ });
+ return memo;
+ };
+
+ // Return the first value which passes a truth test.
+ _.detect = function(obj, iterator, context) {
+ var result;
+ _.each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ _.breakLoop();
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test. Use JavaScript 1.6's
+ // filter(), if it exists.
+ _.select = function(obj, iterator, context) {
+ if (obj && _.isFunction(obj.filter)) return obj.filter(iterator, context);
+ var results = [];
+ _.each(obj, function(value, index, list) {
+ iterator.call(context, value, index, list) && results.push(value);
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ var results = [];
+ _.each(obj, function(value, index, list) {
+ !iterator.call(context, value, index, list) && results.push(value);
+ });
+ return results;
+ };
+
+ // Determine whether all of the elements match a truth test. Delegate to
+ // JavaScript 1.6's every(), if it is present.
+ _.all = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ if (obj && _.isFunction(obj.every)) return obj.every(iterator, context);
+ var result = true;
+ _.each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
+ });
+ return result;
+ };
+
+ // Determine if at least one element in the object matches a truth test. Use
+ // JavaScript 1.6's some(), if it exists.
+ _.any = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ if (obj && _.isFunction(obj.some)) return obj.some(iterator, context);
+ var result = false;
+ _.each(obj, function(value, index, list) {
+ if (result = iterator.call(context, value, index, list)) _.breakLoop();
+ });
+ return result;
+ };
+
+ // Determine if a given value is included in the array or object,
+ // based on '==='.
+ _.include = function(obj, target) {
+ if (obj && _.isFunction(obj.indexOf)) return _.indexOf(obj, target) != -1;
+ var found = false;
+ _.each(obj, function(value) {
+ if (found = value === target) _.breakLoop();
+ });
+ return found;
+ };
+
+ // Invoke a method with arguments on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = _.rest(arguments, 2);
+ return _.map(obj, function(value) {
+ return (method ? value[method] : value).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of map: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Return the maximum item or (item-based computation).
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ var result = {computed : -Infinity};
+ _.each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ var result = {computed : Infinity};
+ _.each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Sort the object's values by a criteria produced by an iterator.
+ _.sortBy = function(obj, iterator, context) {
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }), 'value');
+ };
+
+ // Use a comparator function to figure out at what index an object should
+ // be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator) {
+ iterator = iterator || _.identity;
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >> 1;
+ iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Convert anything iterable into a real, live array.
+ _.toArray = function(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) return iterable.toArray();
+ if (_.isArray(iterable)) return iterable;
+ if (_.isArguments(iterable)) return slice.call(iterable);
+ return _.values(iterable);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ return _.toArray(obj).length;
+ };
+
+ // -------------------------- Array Functions: ------------------------------
+
+ // Get the first element of an array. Passing "n" will return the first N
+ // values in the array. Aliased as "head". The "guard" check allows it to work
+ // with _.map.
+ _.first = function(array, n, guard) {
+ return n && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the first entry of the array. Aliased as "tail".
+ // Especially useful on the arguments object. Passing an "index" will return
+ // the rest of the values in the array from that index onward. The "guard"
+ //check allows it to work with _.map.
+ _.rest = function(array, index, guard) {
+ return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
+ };
+
+ // Get the last element of an array.
+ _.last = function(array) {
+ return array[array.length - 1];
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.select(array, function(value){ return !!value; });
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array) {
+ return _.reduce(array, [], function(memo, value) {
+ if (_.isArray(value)) return memo.concat(_.flatten(value));
+ memo.push(value);
+ return memo;
+ });
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ var values = _.rest(arguments);
+ return _.select(array, function(value){ return !_.include(values, value); });
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ _.uniq = function(array, isSorted) {
+ return _.reduce(array, [], function(memo, el, i) {
+ if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
+ return memo;
+ });
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersect = function(array) {
+ var rest = _.rest(arguments);
+ return _.select(_.uniq(array), function(item) {
+ return _.all(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = _.toArray(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i=0; i<length; i++) results[i] = _.pluck(args, String(i));
+ return results;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),
+ // we need this function. Return the position of the first occurence of an
+ // item in an array, or -1 if the item is not included in the array.
+ _.indexOf = function(array, item) {
+ if (array.indexOf) return array.indexOf(item);
+ for (var i=0, l=array.length; i<l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Provide JavaScript 1.6's lastIndexOf, delegating to the native function,
+ // if possible.
+ _.lastIndexOf = function(array, item) {
+ if (array.lastIndexOf) return array.lastIndexOf(item);
+ var i = array.length;
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python range() function. See:
+ // http://docs.python.org/library/functions.html#range
+ _.range = function(start, stop, step) {
+ var a = _.toArray(arguments);
+ var solo = a.length <= 1;
+ var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1;
+ var len = Math.ceil((stop - start) / step);
+ if (len <= 0) return [];
+ var range = new Array(len);
+ for (var i = start, idx = 0; true; i += step) {
+ if ((step > 0 ? i - stop : stop - i) >= 0) return range;
+ range[idx++] = i;
+ }
+ };
+
+ // ----------------------- Function Functions: ------------------------------
+
+ // Create a function bound to a given object (assigning 'this', and arguments,
+ // optionally). Binding with arguments is also known as 'curry'.
+ _.bind = function(func, obj) {
+ var args = _.rest(arguments, 2);
+ return function() {
+ return func.apply(obj || root, args.concat(_.toArray(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = _.rest(arguments);
+ if (funcs.length == 0) funcs = _.functions(obj);
+ _.each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = _.rest(arguments, 2);
+ return setTimeout(function(){ return func.apply(func, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(_.rest(arguments)));
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func].concat(_.toArray(arguments));
+ return wrapper.apply(wrapper, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = _.toArray(arguments);
+ return function() {
+ var args = _.toArray(arguments);
+ for (var i=funcs.length-1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // ------------------------- Object Functions: ------------------------------
+
+ // Retrieve the names of an object's properties.
+ _.keys = function(obj) {
+ if (_.isArray(obj)) return _.range(0, obj.length);
+ var keys = [];
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ return _.map(obj, _.identity);
+ };
+
+ // Return a sorted list of the function names available in Underscore.
+ _.functions = function(obj) {
+ return _.select(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
+ };
+
+ // Extend a given object with all of the properties in a source object.
+ _.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (_.isArray(obj)) return obj.slice(0);
+ return _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ // Check object identity.
+ if (a === b) return true;
+ // Different types?
+ var atype = typeof(a), btype = typeof(b);
+ if (atype != btype) return false;
+ // Basic equality test (watch out for coercions).
+ if (a == b) return true;
+ // One is falsy and the other truthy.
+ if ((!a && b) || (a && !b)) return false;
+ // One of them implements an isEqual()?
+ if (a.isEqual) return a.isEqual(b);
+ // Check dates' integer values.
+ if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
+ // Both are NaN?
+ if (_.isNaN(a) && _.isNaN(b)) return true;
+ // Compare regular expressions.
+ if (_.isRegExp(a) && _.isRegExp(b))
+ return a.source === b.source &&
+ a.global === b.global &&
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline;
+ // If a is not an object by this point, we can't handle it.
+ if (atype !== 'object') return false;
+ // Check for different array lengths before comparing contents.
+ if (a.length && (a.length !== b.length)) return false;
+ // Nothing else worked, deep compare the contents.
+ var aKeys = _.keys(a), bKeys = _.keys(b);
+ // Different object sizes?
+ if (aKeys.length != bKeys.length) return false;
+ // Recursive comparison of contents.
+ for (var key in a) if (!_.isEqual(a[key], b[key])) return false;
+ return true;
+ };
+
+ // Is a given array or object empty?
+ _.isEmpty = function(obj) {
+ return _.keys(obj).length == 0;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType == 1);
+ };
+
+ // Is a given value an array?
+ _.isArray = function(obj) {
+ return !!(obj && obj.concat && obj.unshift);
+ };
+
+ // Is a given variable an arguments object?
+ _.isArguments = function(obj) {
+ return obj && _.isNumber(obj.length) && !obj.concat && !obj.substr && !obj.apply && !propertyIsEnumerable.call(obj, 'length');
+ };
+
+ // Is a given value a function?
+ _.isFunction = function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+
+ // Is a given value a string?
+ _.isString = function(obj) {
+ return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
+ };
+
+ // Is a given value a number?
+ _.isNumber = function(obj) {
+ return (obj === +obj) || (toString.call(obj) === '[object Number]');
+ };
+
+ // Is a given value a date?
+ _.isDate = function(obj) {
+ return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
+ };
+
+ // Is the given value a regular expression?
+ _.isRegExp = function(obj) {
+ return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
+ };
+
+ // Is the given value NaN -- this one is interesting. NaN != NaN, and
+ // isNaN(undefined) == true, so we make sure it's a number first.
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && isNaN(obj);
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return typeof obj == 'undefined';
+ };
+
+ // -------------------------- Utility Functions: ----------------------------
+
+ // Run Underscore.js in noConflict mode, returning the '_' variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Break out of the middle of an iteration.
+ _.breakLoop = function() {
+ throw breaker;
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ start : '<%',
+ end : '%>',
+ interpolate : /<%=(.+?)%>/g
+ };
+
+ // JavaScript templating a-la ERB, pilfered from John Resig's
+ // "Secrets of the JavaScript Ninja", page 83.
+ // Single-quote fix from Rick Strahl's version.
+ _.template = function(str, data) {
+ var c = _.templateSettings;
+ var fn = new Function('obj',
+ 'var p=[],print=function(){p.push.apply(p,arguments);};' +
+ 'with(obj){p.push(\'' +
+ str.replace(/[\r\t\n]/g, " ")
+ .replace(new RegExp("'(?=[^"+c.end[0]+"]*"+c.end+")","g"),"\t")
+ .split("'").join("\\'")
+ .split("\t").join("'")
+ .replace(c.interpolate, "',$1,'")
+ .split(c.start).join("');")
+ .split(c.end).join("p.push('")
+ + "');}return p.join('');");
+ return data ? fn(data) : fn;
+ };
+
+ // ------------------------------- Aliases ----------------------------------
+
+ _.forEach = _.each;
+ _.foldl = _.inject = _.reduce;
+ _.foldr = _.reduceRight;
+ _.filter = _.select;
+ _.every = _.all;
+ _.some = _.any;
+ _.head = _.first;
+ _.tail = _.rest;
+ _.methods = _.functions;
+
+ // ------------------------ Setup the OOP Wrapper: --------------------------
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj, chain) {
+ return chain ? _(obj).chain() : obj;
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.each(_.functions(_), function(name) {
+ var method = _[name];
+ wrapper.prototype[name] = function() {
+ var args = _.toArray(arguments);
+ unshift.call(args, this._wrapped);
+ return result(method.apply(_, args), this._chain);
+ };
+ });
+
+ // Add all mutator Array functions to the wrapper.
+ _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = Array.prototype[name];
+ wrapper.prototype[name] = function() {
+ method.apply(this._wrapped, arguments);
+ return result(this._wrapped, this._chain);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ _.each(['concat', 'join', 'slice'], function(name) {
+ var method = Array.prototype[name];
+ wrapper.prototype[name] = function() {
+ return result(method.apply(this._wrapped, arguments), this._chain);
+ };
+ });
+
+ // Start chaining a wrapped Underscore object.
+ wrapper.prototype.chain = function() {
+ this._chain = true;
+ return this;
+ };
+
+ // Extracts the result from a wrapped and chained object.
+ wrapper.prototype.value = function() {
+ return this._wrapped;
+ };
+
+})();
diff --git a/lib/underscore/update.sh b/lib/underscore/update.sh
new file mode 100755
index 00000000..6e7bbe7d
--- /dev/null
+++ b/lib/underscore/update.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+underscore=$(dirname $0)
+github='http://github.com/documentcloud/underscore/raw/master'
+
+wget $github/underscore-min.js -O $underscore/underscore-min.js
+wget $github/underscore.js -O $underscore/underscore.js
diff --git a/lib/webtoolkit/webtoolkit.base64.js b/lib/webtoolkit/webtoolkit.base64.js
new file mode 100644
index 00000000..07db4d5d
--- /dev/null
+++ b/lib/webtoolkit/webtoolkit.base64.js
@@ -0,0 +1,142 @@
+/**
+*
+* Base64 encode / decode
+* http://www.webtoolkit.info/
+*
+**/
+
+var Base64 = {
+
+ // private property
+ _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",
+
+ // public method for encoding
+ encode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = Base64._utf8_encode(input);
+
+ while (i < input.length) {
+
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ }
+
+ output = output +
+ this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
+ this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
+
+ }
+
+ return output;
+ },
+
+ // public method for decoding
+ decode : function (input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+
+ while (i < input.length) {
+
+ enc1 = this._keyStr.indexOf(input.charAt(i++));
+ enc2 = this._keyStr.indexOf(input.charAt(i++));
+ enc3 = this._keyStr.indexOf(input.charAt(i++));
+ enc4 = this._keyStr.indexOf(input.charAt(i++));
+
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+
+ output = output + String.fromCharCode(chr1);
+
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+
+ }
+
+ output = Base64._utf8_decode(output);
+
+ return output;
+
+ },
+
+ // private method for UTF-8 encoding
+ _utf8_encode : function (string) {
+ string = string.replace(/\r\n/g,"\n");
+ var utftext = "";
+
+ for (var n = 0; n < string.length; n++) {
+
+ var c = string.charCodeAt(n);
+
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ }
+ else if((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+
+ }
+
+ return utftext;
+ },
+
+ // private method for UTF-8 decoding
+ _utf8_decode : function (utftext) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+
+ while ( i < utftext.length ) {
+
+ c = utftext.charCodeAt(i);
+
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ }
+ else if((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ }
+ else {
+ c2 = utftext.charCodeAt(i+1);
+ c3 = utftext.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+
+ }
+
+ return string;
+ }
+
+}; \ No newline at end of file
diff --git a/nodeserver.sh b/nodeserver.sh
new file mode 100755
index 00000000..a1662e12
--- /dev/null
+++ b/nodeserver.sh
@@ -0,0 +1 @@
+node lib/nodeserver/server.js
diff --git a/scenario/Runner-compiled.html b/scenario/Runner-compiled.html
new file mode 100644
index 00000000..98dbea69
--- /dev/null
+++ b/scenario/Runner-compiled.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../angular-scenario.js"></script>
+ <script type="text/javascript" src="widgets-scenario.js"></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/scenario/Runner.html b/scenario/Runner.html
new file mode 100644
index 00000000..ffa08af9
--- /dev/null
+++ b/scenario/Runner.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../src/scenario/bootstrap.js"></script>
+ <script type="text/javascript" src="widgets-scenario.js"></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/scenario/application-account.html b/scenario/application-account.html
new file mode 100644
index 00000000..a43deffc
--- /dev/null
+++ b/scenario/application-account.html
@@ -0,0 +1,6 @@
+<div ng-controller="AccountController">
+account page goes here!
+ <input type="text" name="name" value="misko"/>
+ <button ng-click="hello()">hello</button>
+</div>
+
diff --git a/scenario/application.html b/scenario/application.html
new file mode 100644
index 00000000..6b6ced69
--- /dev/null
+++ b/scenario/application.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css"></link>
+ <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script>
+ <script>
+ function AccountController(){
+ }
+
+ AccountController.prototype = {
+ hello: function(){
+ alert(this.name);
+ }
+
+ };
+
+ </script>
+ </head>
+ <body ng-init="$window.$scope = this">
+ [ <a href="#login">login</a>
+ | <a href="#account">account</a>
+ ]
+
+ <ng:switch on="$location.hashPath">
+ <div ng-switch-when="login">login screen</div>
+ <ng:include ng-switch-when="account" src="'application-account.html'"></ng:include>
+ </ng:switch>
+
+
+ (( input name ))
+
+ <pre>$location={{$location}}</pre>
+ </body>
+ </html>
diff --git a/scenario/cross-site-post/People.json b/scenario/cross-site-post/People.json
new file mode 100644
index 00000000..de51fd83
--- /dev/null
+++ b/scenario/cross-site-post/People.json
@@ -0,0 +1,4 @@
+[
+ { name: 'Misko', favorite: ['water melon', 'persimmon', 'passion fruit'] },
+ { name: 'Lenka', favorite: ['strawberry'] }
+]
diff --git a/scenario/cross-site-post/index.html b/scenario/cross-site-post/index.html
new file mode 100644
index 00000000..3ff6af85
--- /dev/null
+++ b/scenario/cross-site-post/index.html
@@ -0,0 +1,10 @@
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../../src/angular-bootstrap.js#autobind"></script>
+ </head>
+ <body ng:init="$window.$scope = this; People = $resource('People.json')">
+ <button ng-click="people = People.query()">Load People</button>
+ <pre>people = {{people}}</pre>
+ </body>
+ </html>
diff --git a/scenario/datastore-scenarios.js b/scenario/datastore-scenarios.js
new file mode 100644
index 00000000..6038070b
--- /dev/null
+++ b/scenario/datastore-scenarios.js
@@ -0,0 +1,19 @@
+angular.scenarioDef.datastore = {
+ $before:[
+ {Given:"dataset",
+ dataset:{
+ Book:[{$id:'moby', name:"Moby Dick"},
+ {$id:'gadsby', name:'Great Gadsby'}]
+ }
+ },
+ {Given:"browser", at:"datastore.html#book=moby"},
+ ],
+ checkLoadBook:[
+ {Then:"drainRequestQueue"},
+
+ {Then:"text", at:"{{book.$id}}", should_be:"moby"},
+ {Then:"text", at:"li[$index=0] {{book.name}}", should_be:"Great Gahdsby"},
+ {Then:"text", at:"li[$index=0] {{book.name}}", should_be:"Moby Dick"},
+
+ ]
+};
diff --git a/scenario/datastore.html b/scenario/datastore.html
new file mode 100644
index 00000000..525d3636
--- /dev/null
+++ b/scenario/datastore.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <script type="text/javascript" src="../lib/underscore/underscore.js"></script>
+ <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
+ <script type="text/javascript" src="../src/angular-bootstrap.js"></script>
+ <script type="text/javascript">
+ $(document).ready(function(){angular.compile(document).init();});
+ </script>
+ </head>
+ <body ng-entity="book=Book" ng-init="books=Book.all()">
+ <p>{{book.$id}}</p>
+ <li ng-repeat="book in books.$orderBy('name')">
+ <li>{{book.name}}</li>
+ </li>
+ </body>
+ </html>
diff --git a/scenario/perf.html b/scenario/perf.html
new file mode 100644
index 00000000..cd676918
--- /dev/null
+++ b/scenario/perf.html
@@ -0,0 +1,33 @@
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script>
+ <script type="text/javascript">
+ function PerfCntl(){}
+ PerfCntl.prototype = {
+ createItems: function(){
+ var items = [];
+ for ( var i = 0; i < 1000; i++) {
+ var item = {
+ name: "" + Math.random(),
+ parts: [Math.random(), Math.random()]
+ };
+ items.push(item);
+ }
+ return items;
+ }
+ };
+ </script>
+ </head>
+ <body ng:init="$window.$scope = this; items = createItems()" ng-controller="PerfCntl">
+ <input type="text" name="text"/>
+ <hr/>
+ <ul>
+ <li Xng-repeat="item in items.$filter('').$orderBy('name')"
+ ng-repeat="item in items">
+ {{item.name}} <a href="#{{item.name}}">{{item.parts.join(', ')}}</a>
+ </li>
+ </ul>
+ </body>
+ </html>
diff --git a/scenario/style.css b/scenario/style.css
new file mode 100644
index 00000000..956bdc52
--- /dev/null
+++ b/scenario/style.css
@@ -0,0 +1,7 @@
+th {
+ text-align: left;
+}
+
+tr {
+ border: 1px solid black;
+}
diff --git a/scenario/widgets-scenario.js b/scenario/widgets-scenario.js
new file mode 100644
index 00000000..f4488190
--- /dev/null
+++ b/scenario/widgets-scenario.js
@@ -0,0 +1,25 @@
+describe('widgets', function(){
+ it('should verify that basic widgets work', function(){
+ browser.navigateTo('widgets.html');
+
+ expect('{{text.basic}}').toEqual('');
+ input('text.basic').enter('John');
+ expect('{{text.basic}}').toEqual('John');
+
+ expect('{{text.password}}').toEqual('');
+ input('text.password').enter('secret');
+ expect('{{text.password}}').toEqual('secret');
+
+ expect('{{text.hidden}}').toEqual('hiddenValue');
+
+ expect('{{gender}}').toEqual('male');
+ input('gender').select('female');
+ input('gender').isChecked('female');
+ expect('{{gender}}').toEqual('female');
+
+// expect('{{tea}}').toBeChecked();
+// input('gender').select('female');
+// expect('{{gender}}').toEqual('female');
+
+ });
+});
diff --git a/scenario/widgets-scenarios.old b/scenario/widgets-scenarios.old
new file mode 100644
index 00000000..a1e6c0ed
--- /dev/null
+++ b/scenario/widgets-scenarios.old
@@ -0,0 +1,49 @@
+angular.scenarioDef.widgets = {
+ $before:[
+ {Given:"browser", at:"widgets.html"}
+ ],
+ checkWidgetBinding:[
+ {Then:"text", at:"{{text.basic}}", should_be:""},
+ {When:"enter", text:"John", at:":input[name=text.basic]"},
+ {Then:"text", at:"{{text.basic}}", should_be:"John"},
+
+ {Then:"text", at:"{{gender}}", should_be:"male"},
+ {When:"click", at:"input:radio[value=female]"},
+ {Then:"text", at:"{{gender}}", should_be:"female"},
+
+ {Then:"text", at:"{{tea}}", should_be:"on"},
+ {When:"click", at:"input[name=tea]"},
+ {Then:"text", at:"{{tea}}", should_be:""},
+
+ {Then:"text", at:"{{coffee}}", should_be:""},
+ {When:"click", at:"input[name=coffee]"},
+ {Then:"text", at:"{{coffee}}", should_be:"on"},
+
+ {Then:"text", at:"{{count}}", should_be:0},
+ {When:"click", at:"form :button"},
+ {When:"click", at:"form :submit"},
+ {When:"click", at:"form :image"},
+ {Then:"text", at:"{{count}}", should_be:3},
+
+ {Then:"text", at:"{{select}}", should_be:"A"},
+ {When:"select", at:"select[name=select]", option:"B"},
+ {Then:"text", at:"{{select}}", should_be:"B"},
+
+ {Then:"text", at:"{{multiple}}", should_be:"[]"},
+ {When:"select", at:"select[name=multiple]", option:"A"},
+ {Then:"text", at:"{{multiple}}", should_be:["A"]},
+ {When:"select", at:"select[name=multiple]", option:"B"},
+ {Then:"text", at:"{{multiple}}", should_be:["A", "B"]},
+ {When:"select", at:"select[name=multiple]", option:"A"},
+ {Then:"text", at:"{{multiple}}", should_be:["B"]},
+
+ {Then:"text", at:"{{hidden}}", should_be:"hiddenValue"},
+
+ {Then:"text", at:"{{password}}", should_be:"passwordValue"},
+ {When:"enter", text:"reset", at:":input[name=password]"},
+ {Then:"text", at:"{{password}}", should_be:"reset"},
+ ],
+ checkNewWidgetEmpty:[
+ {Then:"text", at:"{{name}}", should_be:""},
+ ]
+};
diff --git a/scenario/widgets.html b/scenario/widgets.html
new file mode 100644
index 00000000..86269e86
--- /dev/null
+++ b/scenario/widgets.html
@@ -0,0 +1,98 @@
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script>
+ </head>
+ <body ng:init="$window.$scope = this">
+ <table>
+ <tr>
+ <th width="330">Description</th>
+ <th>Test</th>
+ <th>Result</th>
+ </tr>
+ <tr><th colspan="3">Input text field</th></tr>
+ <tr>
+ <td>basic</td>
+ <td>
+ <input type="text" name="text.basic"/>
+ </td>
+ <td>text.basic={{text.basic}}</td>
+ </tr>
+ <tr>
+ <td>password</td>
+ <td><input type="password" name="text.password" /></td>
+ <td>text.password={{text.password}}</td>
+ </tr>
+ <tr>
+ <td>hidden</td>
+ <td><input type="hidden" name="text.hidden" value="hiddenValue" /></td>
+ <td>text.hidden={{text.hidden}}</td>
+ </tr>
+ <tr><th colspan="3">Input selection field</th></tr>
+ <tr>
+ <td>radio</td>
+ <td>
+ <input type="radio" name="gender" value="female"/> Female <br/>
+ <input type="radio" name="gender" value="male" checked="checked"/> Male
+ </td>
+ <td>gender={{gender}}</td>
+ </tr>
+ <tr>
+ <td>checkbox</td>
+ <td>
+ <input type="checkbox" name="checkbox.tea" checked value="on"/> Tea<br/>
+ <input type="checkbox" name="checkbox.coffee" value="on"/> Coffe
+ </td>
+ <td>
+ <pre>checkbox={{checkbox}}</pre>
+ </td>
+ </tr>
+ <tr>
+ <td>select</td>
+ <td>
+ <select name="select">
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ </td>
+ <td>select={{select}}</td>
+ </tr>
+ <tr>
+ <td>multiselect</td>
+ <td>
+ <select name="multiselect" multiple>
+ <option>A</option>
+ <option>B</option>
+ <option>C</option>
+ </select>
+ </td>
+ <td>multiselect={{multiselect}}</td>
+ </tr>
+ <tr><th colspan="3">Buttons</th></tr>
+ <tr>
+ <td>ng-change<br/>ng-click</td>
+ <td ng:init="button.count = 0">
+ <form>
+ <input type="button" value="button" ng-change="button.count = button.count + 1"/> <br/>
+ <input type="submit" value="submit" ng-change="button.count = button.count + 1"/><br/>
+ <input type="image" src="" ng-change="button.count = button.count + 1"/><br/>
+ <a href="" ng:click="button.count = button.count + 1">action</a>
+ </form>
+ </td>
+ <td>button={{button}}</td>
+ </tr>
+ <tr><th colspan="3">Repeaters</th></tr>
+ <tr>
+ <td>ng-repeat</td>
+ <td>
+ <ul>
+ <li ng-repeat="name in ['misko', 'adam']">{{name}}</li>
+ </ul>
+ </td>
+ <td></td>
+ </tr>
+ </table>
+ </body>
+ </html>
diff --git a/server.sh b/server.sh
new file mode 100755
index 00000000..7690cf8a
--- /dev/null
+++ b/server.sh
@@ -0,0 +1 @@
+java -jar lib/jstestdriver/JsTestDriver.jar --port 9876
diff --git a/src/Angular.js b/src/Angular.js
new file mode 100644
index 00000000..2b26c88d
--- /dev/null
+++ b/src/Angular.js
@@ -0,0 +1,401 @@
+////////////////////////////////////
+
+if (typeof document.getAttribute == 'undefined')
+ document.getAttribute = function() {};
+
+if (!window['console']) window['console']={'log':noop, 'error':noop};
+
+var consoleNode,
+ PRIORITY_FIRST = -99999,
+ PRIORITY_WATCH = -1000,
+ PRIORITY_LAST = 99999,
+ PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},
+ NOOP = 'noop',
+ NG_EXCEPTION = 'ng-exception',
+ NG_VALIDATION_ERROR = 'ng-validation-error',
+ jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
+ _ = window['_'],
+ msie = !!/(msie) ([\w.]+)/.exec(lowercase(navigator.userAgent)),
+ jqLite = jQuery || jqLiteWrap,
+ slice = Array.prototype.slice,
+ angular = window['angular'] || (window['angular'] = {}),
+ angularTextMarkup = extensionMap(angular, 'textMarkup'),
+ angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
+ angularDirective = extensionMap(angular, 'directive'),
+ angularWidget = extensionMap(angular, 'widget'),
+ angularValidator = extensionMap(angular, 'validator'),
+ angularFilter = extensionMap(angular, 'filter'),
+ angularFormatter = extensionMap(angular, 'formatter'),
+ angularService = extensionMap(angular, 'service'),
+ angularCallbacks = extensionMap(angular, 'callbacks'),
+ nodeName;
+
+function angularAlert(){
+ log(arguments); window.alert.apply(window, arguments);
+}
+
+function foreach(obj, iterator, context) {
+ var key;
+ if (obj) {
+ if (isFunction(obj)){
+ for (key in obj) {
+ if (key != 'prototype' && key != 'length' && key != 'name' && obj.hasOwnProperty(key)) {
+ iterator.call(context, obj[key], key);
+ }
+ }
+ } else if (obj.forEach) {
+ obj.forEach(iterator, context);
+ } else if (isObject(obj) && isNumber(obj.length)) {
+ for (key = 0; key < obj.length; key++)
+ iterator.call(context, obj[key], key);
+ } else {
+ for (key in obj)
+ iterator.call(context, obj[key], key);
+ }
+ }
+ return obj;
+}
+
+function foreachSorted(obj, iterator, context) {
+ var keys = [];
+ for (var key in obj) keys.push(key);
+ keys.sort();
+ for ( var i = 0; i < keys.length; i++) {
+ iterator.call(context, obj[keys[i]], keys[i]);
+ }
+ return keys;
+}
+
+
+function extend(dst) {
+ foreach(arguments, function(obj){
+ if (obj !== dst) {
+ foreach(obj, function(value, key){
+ dst[key] = value;
+ });
+ }
+ });
+ return dst;
+}
+
+function noop() {}
+function identity($) {return $;}
+function extensionMap(angular, name) {
+ var extPoint;
+ return angular[name] || (extPoint = angular[name] = function (name, fn, prop){
+ if (isDefined(fn)) {
+ extPoint[name] = extend(fn, prop || {});
+ }
+ return extPoint[name];
+ });
+}
+
+function jqLiteWrap(element) {
+ // for some reasons the parentNode of an orphan looks like null but its typeof is object.
+ if (element) {
+ if (isString(element)) {
+ var div = document.createElement('div');
+ div.innerHTML = element;
+ element = new JQLite(div.childNodes);
+ } else if (!(element instanceof JQLite) && isElement(element)) {
+ element = new JQLite(element);
+ }
+ }
+ return element;
+}
+function isUndefined(value){ return typeof value == 'undefined'; }
+function isDefined(value){ return typeof value != 'undefined'; }
+function isObject(value){ return typeof value == 'object';}
+function isString(value){ return typeof value == 'string';}
+function isNumber(value){ return typeof value == 'number';}
+function isArray(value) { return value instanceof Array; }
+function isFunction(value){ return typeof value == 'function';}
+function isTextNode(node) { return nodeName(node) == '#text'; }
+function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }
+function uppercase(value){ return isString(value) ? value.toUpperCase() : value; }
+function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
+function isElement(node) {
+ return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery));
+}
+
+function HTML(html) {
+ this.html = html;
+}
+
+if (msie) {
+ nodeName = function(element) {
+ element = element[0] || element;
+ return (element.scopeName && element.scopeName != 'HTML' ) ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName;
+ };
+} else {
+ nodeName = function(element) {
+ return (element[0] || element).nodeName;
+ };
+}
+
+function isVisible(element) {
+ var rect = element[0].getBoundingClientRect(),
+ width = (rect.width || (rect.right||0 - rect.left||0)),
+ height = (rect.height || (rect.bottom||0 - rect.top||0));
+ return width>0 && height>0;
+}
+
+function map(obj, iterator, context) {
+ var results = [];
+ foreach(obj, function(value, index, list) {
+ results.push(iterator.call(context, value, index, list));
+ });
+ return results;
+}
+function size(obj) {
+ var size = 0;
+ if (obj) {
+ if (isNumber(obj.length)) {
+ return obj.length;
+ } else if (isObject(obj)){
+ for (key in obj)
+ size++;
+ }
+ }
+ return size;
+}
+function includes(array, obj) {
+ for ( var i = 0; i < array.length; i++) {
+ if (obj === array[i]) return true;
+ }
+ return false;
+}
+
+function indexOf(array, obj) {
+ for ( var i = 0; i < array.length; i++) {
+ if (obj === array[i]) return i;
+ }
+ return -1;
+}
+
+function log(a, b, c){
+ var console = window['console'];
+ switch(arguments.length) {
+ case 1:
+ console['log'](a);
+ break;
+ case 2:
+ console['log'](a, b);
+ break;
+ default:
+ console['log'](a, b, c);
+ break;
+ }
+}
+
+function error(a, b, c){
+ var console = window['console'];
+ switch(arguments.length) {
+ case 1:
+ console['error'](a);
+ break;
+ case 2:
+ console['error'](a, b);
+ break;
+ default:
+ console['error'](a, b, c);
+ break;
+ }
+}
+
+function consoleLog(level, objs) {
+ var log = document.createElement("div");
+ log.className = level;
+ var msg = "";
+ var sep = "";
+ for ( var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ msg += sep + (typeof obj == 'string' ? obj : toJson(obj));
+ sep = " ";
+ }
+ log.appendChild(document.createTextNode(msg));
+ consoleNode.appendChild(log);
+}
+
+function isLeafNode (node) {
+ if (node) {
+ switch (node.nodeName) {
+ case "OPTION":
+ case "PRE":
+ case "TITLE":
+ return true;
+ }
+ }
+ return false;
+}
+
+function copy(source, destination){
+ if (!destination) {
+ if (source) {
+ if (isArray(source)) {
+ return copy(source, []);
+ } else if (isObject(source)) {
+ return copy(source, {});
+ }
+ }
+ return source;
+ } else {
+ if (isArray(source)) {
+ while(destination.length) {
+ destination.pop();
+ }
+ for ( var i = 0; i < source.length; i++) {
+ destination.push(copy(source[i]));
+ }
+ } else {
+ foreach(destination, function(value, key){
+ delete destination[key];
+ });
+ for ( var key in source) {
+ destination[key] = copy(source[key]);
+ }
+ }
+ return destination;
+ }
+}
+
+function setHtml(node, html) {
+ if (isLeafNode(node)) {
+ if (msie) {
+ node.innerText = html;
+ } else {
+ node.textContent = html;
+ }
+ } else {
+ node.innerHTML = html;
+ }
+}
+
+function escapeHtml(html) {
+ if (!html || !html.replace)
+ return html;
+ return html.
+ replace(/&/g, '&amp;').
+ replace(/</g, '&lt;').
+ replace(/>/g, '&gt;');
+}
+
+
+function isRenderableElement(element) {
+ var name = element && element[0] && element[0].nodeName;
+ return name && name.charAt(0) != '#' &&
+ !includes(['TR', 'COL', 'COLGROUP', 'TBODY', 'THEAD', 'TFOOT'], name);
+}
+function elementError(element, type, error) {
+ while (!isRenderableElement(element)) {
+ element = element.parent() || jqLite(document.body);
+ }
+ if (element[0]['$NG_ERROR'] !== error) {
+ element[0]['$NG_ERROR'] = error;
+ if (error) {
+ element.addClass(type);
+ element.attr(type, error);
+ } else {
+ element.removeClass(type);
+ element.removeAttr(type);
+ }
+ }
+}
+
+function escapeAttr(html) {
+ if (!html || !html.replace)
+ return html;
+ return html.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g,
+ '&quot;');
+}
+
+function bind(_this, _function) {
+ if (!isFunction(_function))
+ throw "Not a function!";
+ var curryArgs = slice.call(arguments, 2, arguments.length);
+ return function() {
+ return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length)));
+ };
+}
+
+function outerHTML(node) {
+ var temp = document.createElement('div');
+ temp.appendChild(node);
+ var outerHTML = temp.innerHTML;
+ temp.removeChild(node);
+ return outerHTML;
+}
+
+function toBoolean(value) {
+ if (value && value.length !== 0) {
+ var v = lowercase("" + value);
+ value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == '[]');
+ } else {
+ value = false;
+ }
+ return value;
+}
+
+function merge(src, dst) {
+ for ( var key in src) {
+ var value = dst[key];
+ var type = typeof value;
+ if (type == 'undefined') {
+ dst[key] = fromJson(toJson(src[key]));
+ } else if (type == 'object' && value.constructor != array &&
+ key.substring(0, 1) != "$") {
+ merge(src[key], value);
+ }
+ }
+}
+
+function compile(element, parentScope, overrides) {
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget),
+ $element = jqLite(element),
+ parent = extend({}, parentScope);
+ parent.$element = $element;
+ return compiler.compile($element)($element, parent, overrides);
+}
+/////////////////////////////////////////////////
+
+function parseKeyValue(keyValue) {
+ var obj = {}, key_value, key;
+ foreach((keyValue || "").split('&'), function(keyValue){
+ if (keyValue) {
+ key_value = keyValue.split('=');
+ key = decodeURIComponent(key_value[0]);
+ obj[key] = key_value[1] ? decodeURIComponent(key_value[1]) : true;
+ }
+ });
+ return obj;
+}
+
+function toKeyValue(obj) {
+ var parts = [];
+ foreach(obj, function(value, key){
+ parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+ });
+ return parts.length ? parts.join('&') : '';
+}
+
+function angularInit(config){
+ if (config.autobind) {
+ var scope = compile(window.document, null, {'$config':config});
+ // TODO default to the source of angular.js
+ scope.$browser.addCss('css/angular.css');
+ scope.$init();
+ }
+}
+
+function angularJsConfig(document) {
+ var filename = /(.*)\/angular(-(.*))?.js(#(.*))?/,
+ scripts = document.getElementsByTagName("SCRIPT"),
+ match;
+ for(var j = 0; j < scripts.length; j++) {
+ match = (scripts[j].src || "").match(filename);
+ if (match) {
+ return match[5];
+ }
+ }
+ return "";
+}
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
new file mode 100644
index 00000000..7230c3e5
--- /dev/null
+++ b/src/AngularPublic.js
@@ -0,0 +1,29 @@
+var browserSingleton;
+angularService('$browser', function browserFactory(){
+ if (!browserSingleton) {
+ browserSingleton = new Browser(window.location, window.document);
+ browserSingleton.startUrlWatcher();
+ browserSingleton.bind();
+ }
+ return browserSingleton;
+});
+
+extend(angular, {
+ 'element': jqLite,
+ 'compile': compile,
+ 'scope': createScope,
+ 'copy': copy,
+ 'extend': extend,
+ 'foreach': foreach,
+ 'noop':noop,
+ 'bind':bind,
+ 'identity':identity,
+ 'isUndefined': isUndefined,
+ 'isDefined': isDefined,
+ 'isString': isString,
+ 'isFunction': isFunction,
+ 'isObject': isObject,
+ 'isNumber': isNumber,
+ 'isArray': isArray
+});
+
diff --git a/src/Browser.js b/src/Browser.js
new file mode 100644
index 00000000..0552b3ae
--- /dev/null
+++ b/src/Browser.js
@@ -0,0 +1,130 @@
+//////////////////////////////
+// Browser
+//////////////////////////////
+
+function Browser(location, document) {
+ this.delay = 50;
+ this.expectedUrl = location.href;
+ this.urlListeners = [];
+ this.hoverListener = noop;
+ this.isMock = false;
+ this.outstandingRequests = { count: 0, callbacks:[]};
+
+ this.XHR = window.XMLHttpRequest || function () {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+ };
+ this.setTimeout = function(fn, delay) {
+ window.setTimeout(fn, delay);
+ };
+
+ this.location = location;
+ this.document = jqLite(document);
+ this.body = jqLite(document.body);
+}
+
+Browser.prototype = {
+
+ bind: function() {
+ var self = this;
+ self.document.bind("mouseover", function(event){
+ self.hoverListener(jqLite(msie ? event.srcElement : event.target), true);
+ return true;
+ });
+ self.document.bind("mouseleave mouseout click dblclick keypress keyup", function(event){
+ self.hoverListener(jqLite(event.target), false);
+ return true;
+ });
+ },
+
+ hover: function(hoverListener) {
+ this.hoverListener = hoverListener;
+ },
+
+ addCss: function(url) {
+ var doc = this.document[0],
+ head = jqLite(doc.getElementsByTagName('head')[0]),
+ link = jqLite(doc.createElement('link'));
+ link.attr('rel', 'stylesheet');
+ link.attr('type', 'text/css');
+ link.attr('href', url);
+ head.append(link);
+ },
+
+ xhr: function(method, url, post, callback){
+ if (isFunction(post)) {
+ callback = post;
+ post = null;
+ }
+ var xhr = new this.XHR(),
+ self = this;
+ xhr.open(method, url, true);
+ this.outstandingRequests.count ++;
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ try {
+ callback(xhr.status || 200, xhr.responseText);
+ } finally {
+ self.outstandingRequests.count--;
+ self.processRequestCallbacks();
+ }
+ }
+ };
+ xhr.send(post || '');
+ },
+
+ processRequestCallbacks: function(){
+ if (this.outstandingRequests.count === 0) {
+ while(this.outstandingRequests.callbacks.length) {
+ try {
+ this.outstandingRequests.callbacks.pop()();
+ } catch (e) {
+ }
+ }
+ }
+ },
+
+ notifyWhenNoOutstandingRequests: function(callback){
+ if (this.outstandingRequests.count === 0) {
+ callback();
+ } else {
+ this.outstandingRequests.callbacks.push(callback);
+ }
+ },
+
+ watchUrl: function(fn){
+ this.urlListeners.push(fn);
+ },
+
+ startUrlWatcher: function() {
+ var self = this;
+ (function pull () {
+ if (self.expectedUrl !== self.location.href) {
+ foreach(self.urlListeners, function(listener){
+ try {
+ listener(self.location.href);
+ } catch (e) {
+ error(e);
+ }
+ });
+ self.expectedUrl = self.location.href;
+ }
+ self.setTimeout(pull, self.delay);
+ })();
+ },
+
+ setUrl: function(url) {
+ var existingURL = this.location.href;
+ if (!existingURL.match(/#/)) existingURL += '#';
+ if (!url.match(/#/)) url += '#';
+ if (existingURL != url) {
+ this.location.href = this.expectedUrl = url;
+ }
+ },
+
+ getUrl: function() {
+ return this.location.href;
+ }
+};
diff --git a/src/Compiler.js b/src/Compiler.js
new file mode 100644
index 00000000..c8910c27
--- /dev/null
+++ b/src/Compiler.js
@@ -0,0 +1,212 @@
+/**
+ * Template provides directions an how to bind to a given element.
+ * It contains a list of init functions which need to be called to
+ * bind to a new instance of elements. It also provides a list
+ * of child paths which contain child templates
+ */
+function Template(priority) {
+ this.paths = [];
+ this.children = [];
+ this.inits = [];
+ this.priority = priority || 0;
+}
+
+Template.prototype = {
+ init: function(element, scope) {
+ var inits = {};
+ this.collectInits(element, inits);
+ foreachSorted(inits, function(queue){
+ foreach(queue, function(fn){
+ fn(scope);
+ });
+ });
+ },
+
+ collectInits: function(element, inits) {
+ var queue = inits[this.priority];
+ if (!queue) {
+ inits[this.priority] = queue = [];
+ }
+ element = jqLite(element);
+ foreach(this.inits, function(fn) {
+ queue.push(function(scope) {
+ scope.$tryEval(fn, element, element);
+ });
+ });
+
+ var i,
+ childNodes = element[0].childNodes,
+ children = this.children,
+ paths = this.paths,
+ length = paths.length;
+ for (i = 0; i < length; i++) {
+ children[i].collectInits(childNodes[paths[i]], inits);
+ }
+ },
+
+
+ addInit:function(init) {
+ if (init) {
+ this.inits.push(init);
+ }
+ },
+
+
+ addChild: function(index, template) {
+ if (template) {
+ this.paths.push(index);
+ this.children.push(template);
+ }
+ },
+
+ empty: function() {
+ return this.inits.length === 0 && this.paths.length === 0;
+ }
+};
+
+///////////////////////////////////
+//Compiler
+//////////////////////////////////
+function Compiler(textMarkup, attrMarkup, directives, widgets){
+ this.textMarkup = textMarkup;
+ this.attrMarkup = attrMarkup;
+ this.directives = directives;
+ this.widgets = widgets;
+}
+
+Compiler.prototype = {
+ compile: function(rawElement) {
+ rawElement = jqLite(rawElement);
+ var index = 0,
+ template,
+ parent = rawElement.parent();
+ if (parent && parent[0]) {
+ parent = parent[0];
+ for(var i = 0; i < parent.childNodes.length; i++) {
+ if (parent.childNodes[i] == rawElement[0]) {
+ index = i;
+ }
+ }
+ }
+ template = this.templatize(rawElement, index, 0) || new Template();
+ return function(element, parentScope){
+ element = jqLite(element);
+ var scope = parentScope && parentScope.$eval ?
+ parentScope :
+ createScope(parentScope || {}, angularService);
+ return extend(scope, {
+ $element:element,
+ $init: function() {
+ template.init(element, scope);
+ scope.$eval();
+ delete scope.$init;
+ return scope;
+ }
+ });
+ };
+ },
+
+ templatize: function(element, elementIndex, priority){
+ var self = this,
+ widget,
+ directiveFns = self.directives,
+ descend = true,
+ directives = true,
+ template,
+ selfApi = {
+ compile: bind(self, self.compile),
+ comment:function(text) {return jqLite(document.createComment(text));},
+ element:function(type) {return jqLite(document.createElement(type));},
+ text:function(text) {return jqLite(document.createTextNode(text));},
+ descend: function(value){ if(isDefined(value)) descend = value; return descend;},
+ directives: function(value){ if(isDefined(value)) directives = value; return directives;}
+ };
+ priority = element.attr('ng-eval-order') || priority || 0;
+ if (isString(priority)) {
+ priority = PRIORITY[uppercase(priority)] || 0;
+ }
+ template = new Template(priority);
+ eachAttribute(element, function(value, name){
+ if (!widget) {
+ if (widget = self.widgets['@' + name]) {
+ widget = bind(selfApi, widget, value, element);
+ }
+ }
+ });
+ if (!widget) {
+ if (widget = self.widgets[nodeName(element)]) {
+ widget = bind(selfApi, widget, element);
+ }
+ }
+ if (widget) {
+ descend = false;
+ directives = false;
+ var parent = element.parent();
+ template.addInit(widget.call(selfApi, element));
+ if (parent && parent[0]) {
+ element = jqLite(parent[0].childNodes[elementIndex]);
+ }
+ }
+ if (descend){
+ // process markup for text nodes only
+ eachTextNode(element, function(textNode){
+ var text = textNode.text();
+ foreach(self.textMarkup, function(markup){
+ markup.call(selfApi, text, textNode, element);
+ });
+ });
+ }
+
+ if (directives) {
+ // Process attributes/directives
+ eachAttribute(element, function(value, name){
+ foreach(self.attrMarkup, function(markup){
+ markup.call(selfApi, value, name, element);
+ });
+ });
+ eachAttribute(element, function(value, name){
+ template.addInit((directiveFns[name]||noop).call(selfApi, value, element));
+ });
+ }
+ // Process non text child nodes
+ if (descend) {
+ eachNode(element, function(child, i){
+ template.addChild(i, self.templatize(child, i, priority));
+ });
+ }
+ return template.empty() ? null : template;
+ }
+};
+
+function eachTextNode(element, fn){
+ var i, chldNodes = element[0].childNodes || [], chld;
+ for (i = 0; i < chldNodes.length; i++) {
+ if(isTextNode(chld = chldNodes[i])) {
+ fn(jqLite(chld), i);
+ }
+ }
+}
+
+function eachNode(element, fn){
+ var i, chldNodes = element[0].childNodes || [], chld;
+ for (i = 0; i < chldNodes.length; i++) {
+ if(!isTextNode(chld = chldNodes[i])) {
+ fn(jqLite(chld), i);
+ }
+ }
+}
+
+function eachAttribute(element, fn){
+ var i, attrs = element[0].attributes || [], chld, attr, name, value, attrValue = {};
+ for (i = 0; i < attrs.length; i++) {
+ attr = attrs[i];
+ name = attr.name.replace(':', '-');
+ value = attr.value;
+ if (msie && name == 'href') {
+ value = decodeURIComponent(element[0].getAttribute(name, 2));
+ }
+ attrValue[name] = value;
+ }
+ foreachSorted(attrValue, fn);
+}
+
diff --git a/src/JSON.js b/src/JSON.js
new file mode 100644
index 00000000..340b075a
--- /dev/null
+++ b/src/JSON.js
@@ -0,0 +1,105 @@
+array = [].constructor;
+
+function toJson(obj, pretty){
+ var buf = [];
+ toJsonArray(buf, obj, pretty ? "\n " : null, []);
+ return buf.join('');
+}
+
+function toPrettyJson(obj) {
+ return toJson(obj, true);
+}
+
+function fromJson(json) {
+ if (!json) return json;
+ try {
+ var parser = new Parser(json, true);
+ var expression = parser.primary();
+ parser.assertAllConsumed();
+ return expression();
+ } catch (e) {
+ error("fromJson error: ", json, e);
+ throw e;
+ }
+}
+
+angular['toJson'] = toJson;
+angular['fromJson'] = fromJson;
+
+function toJsonArray(buf, obj, pretty, stack){
+ if (typeof obj == "object") {
+ if (includes(stack, obj)) {
+ buf.push("RECURSION");
+ return;
+ }
+ stack.push(obj);
+ }
+ var type = typeof obj;
+ if (obj === null) {
+ buf.push("null");
+ } else if (type === 'function') {
+ return;
+ } else if (type === 'boolean') {
+ buf.push('' + obj);
+ } else if (type === 'number') {
+ if (isNaN(obj)) {
+ buf.push('null');
+ } else {
+ buf.push('' + obj);
+ }
+ } else if (type === 'string') {
+ return buf.push(angular['String']['quoteUnicode'](obj));
+ } else if (type === 'object') {
+ if (obj instanceof Array) {
+ buf.push("[");
+ var len = obj.length;
+ var sep = false;
+ for(var i=0; i<len; i++) {
+ var item = obj[i];
+ if (sep) buf.push(",");
+ if (typeof item == 'function' || typeof item == 'undefined') {
+ buf.push("null");
+ } else {
+ toJsonArray(buf, item, pretty, stack);
+ }
+ sep = true;
+ }
+ buf.push("]");
+ } else if (obj instanceof Date) {
+ buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj)));
+ } else {
+ buf.push("{");
+ if (pretty) buf.push(pretty);
+ var comma = false;
+ var childPretty = pretty ? pretty + " " : false;
+ var keys = [];
+ for(var k in obj) {
+ if (k.indexOf('$$') === 0 || obj[k] === undefined)
+ continue;
+ keys.push(k);
+ }
+ keys.sort();
+ for ( var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
+ var key = keys[keyIndex];
+ try {
+ var value = obj[key];
+ if (typeof value != 'function') {
+ if (comma) {
+ buf.push(",");
+ if (pretty) buf.push(pretty);
+ }
+ buf.push(angular['String']['quote'](key));
+ buf.push(":");
+ toJsonArray(buf, value, childPretty, stack);
+ comma = true;
+ }
+ } catch (e) {
+ }
+ }
+ buf.push("}");
+ }
+ }
+ if (typeof obj == "object") {
+ stack.pop();
+ }
+}
diff --git a/src/Parser.js b/src/Parser.js
new file mode 100644
index 00000000..df270792
--- /dev/null
+++ b/src/Parser.js
@@ -0,0 +1,730 @@
+function Lexer(text, parsStrings){
+ this.text = text;
+ // UTC dates have 20 characters, we send them through parser
+ this.dateParseLength = parsStrings ? 20 : -1;
+ this.tokens = [];
+ this.index = 0;
+}
+
+Lexer.OPERATORS = {
+ 'null':function(self){return null;},
+ 'true':function(self){return true;},
+ 'false':function(self){return false;},
+ 'undefined':noop,
+ '+':function(self, a,b){return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
+ '-':function(self, a,b){return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
+ '*':function(self, a,b){return a*b;},
+ '/':function(self, a,b){return a/b;},
+ '%':function(self, a,b){return a%b;},
+ '^':function(self, a,b){return a^b;},
+ '=':function(self, a,b){return setter(self, a, b);},
+ '==':function(self, a,b){return a==b;},
+ '!=':function(self, a,b){return a!=b;},
+ '<':function(self, a,b){return a<b;},
+ '>':function(self, a,b){return a>b;},
+ '<=':function(self, a,b){return a<=b;},
+ '>=':function(self, a,b){return a>=b;},
+ '&&':function(self, a,b){return a&&b;},
+ '||':function(self, a,b){return a||b;},
+ '&':function(self, a,b){return a&b;},
+// '|':function(self, a,b){return a|b;},
+ '|':function(self, a,b){return b(self, a);},
+ '!':function(self, a){return !a;}
+};
+Lexer.ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
+
+Lexer.prototype = {
+ peek: function() {
+ if (this.index + 1 < this.text.length) {
+ return this.text.charAt(this.index + 1);
+ } else {
+ return false;
+ }
+ },
+
+ parse: function() {
+ var tokens = this.tokens;
+ var OPERATORS = Lexer.OPERATORS;
+ var canStartRegExp = true;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '"' || ch == "'") {
+ this.readString(ch);
+ canStartRegExp = true;
+ } else if (ch == '(' || ch == '[') {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ } else if (ch == '{' ) {
+ var peekCh = this.peek();
+ if (peekCh == ':' || peekCh == '(') {
+ tokens.push({index:this.index, text:ch + peekCh});
+ this.index++;
+ } else {
+ tokens.push({index:this.index, text:ch});
+ }
+ this.index++;
+ canStartRegExp = true;
+ } else if (ch == ')' || ch == ']' || ch == '}' ) {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ canStartRegExp = false;
+ } else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ canStartRegExp = true;
+ } else if ( canStartRegExp && ch == '/' ) {
+ this.readRegexp();
+ canStartRegExp = false;
+ } else if ( this.isNumber(ch) ) {
+ this.readNumber();
+ canStartRegExp = false;
+ } else if (this.isIdent(ch)) {
+ this.readIdent();
+ canStartRegExp = false;
+ } else if (this.isWhitespace(ch)) {
+ this.index++;
+ } else {
+ var ch2 = ch + this.peek();
+ var fn = OPERATORS[ch];
+ var fn2 = OPERATORS[ch2];
+ if (fn2) {
+ tokens.push({index:this.index, text:ch2, fn:fn2});
+ this.index += 2;
+ } else if (fn) {
+ tokens.push({index:this.index, text:ch, fn:fn});
+ this.index += 1;
+ } else {
+ throw "Lexer Error: Unexpected next character [" +
+ this.text.substring(this.index) +
+ "] in expression '" + this.text +
+ "' at column '" + (this.index+1) + "'.";
+ }
+ canStartRegExp = true;
+ }
+ }
+ return tokens;
+ },
+
+ isNumber: function(ch) {
+ return '0' <= ch && ch <= '9';
+ },
+
+ isWhitespace: function(ch) {
+ return ch == ' ' || ch == '\r' || ch == '\t' ||
+ ch == '\n' || ch == '\v';
+ },
+
+ isIdent: function(ch) {
+ return 'a' <= ch && ch <= 'z' ||
+ 'A' <= ch && ch <= 'Z' ||
+ '_' == ch || ch == '$';
+ },
+
+ readNumber: function() {
+ var number = "";
+ var start = this.index;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '.' || this.isNumber(ch)) {
+ number += ch;
+ } else {
+ break;
+ }
+ this.index++;
+ }
+ number = 1 * number;
+ this.tokens.push({index:start, text:number,
+ fn:function(){return number;}});
+ },
+
+ readIdent: function() {
+ var ident = "";
+ var start = this.index;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '.' || this.isIdent(ch) || this.isNumber(ch)) {
+ ident += ch;
+ } else {
+ break;
+ }
+ this.index++;
+ }
+ var fn = Lexer.OPERATORS[ident];
+ if (!fn) {
+ fn = getterFn(ident);
+ fn.isAssignable = ident;
+ }
+ this.tokens.push({index:start, text:ident, fn:fn});
+ },
+
+ readString: function(quote) {
+ var start = this.index;
+ var dateParseLength = this.dateParseLength;
+ this.index++;
+ var string = "";
+ var rawString = quote;
+ var escape = false;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ rawString += ch;
+ if (escape) {
+ if (ch == 'u') {
+ var hex = this.text.substring(this.index + 1, this.index + 5);
+ this.index += 4;
+ string += String.fromCharCode(parseInt(hex, 16));
+ } else {
+ var rep = Lexer.ESCAPE[ch];
+ if (rep) {
+ string += rep;
+ } else {
+ string += ch;
+ }
+ }
+ escape = false;
+ } else if (ch == '\\') {
+ escape = true;
+ } else if (ch == quote) {
+ this.index++;
+ this.tokens.push({index:start, text:rawString, string:string,
+ fn:function(){
+ return (string.length == dateParseLength) ?
+ angular['String']['toDate'](string) : string;
+ }});
+ return;
+ } else {
+ string += ch;
+ }
+ this.index++;
+ }
+ throw "Lexer Error: Unterminated quote [" +
+ this.text.substring(start) + "] starting at column '" +
+ (start+1) + "' in expression '" + this.text + "'.";
+ },
+
+ readRegexp: function(quote) {
+ var start = this.index;
+ this.index++;
+ var regexp = "";
+ var escape = false;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (escape) {
+ regexp += ch;
+ escape = false;
+ } else if (ch === '\\') {
+ regexp += ch;
+ escape = true;
+ } else if (ch === '/') {
+ this.index++;
+ var flags = "";
+ if (this.isIdent(this.text.charAt(this.index))) {
+ this.readIdent();
+ flags = this.tokens.pop().text;
+ }
+ var compiledRegexp = new RegExp(regexp, flags);
+ this.tokens.push({index:start, text:regexp, flags:flags,
+ fn:function(){return compiledRegexp;}});
+ return;
+ } else {
+ regexp += ch;
+ }
+ this.index++;
+ }
+ throw "Lexer Error: Unterminated RegExp [" +
+ this.text.substring(start) + "] starting at column '" +
+ (start+1) + "' in expression '" + this.text + "'.";
+ }
+};
+
+/////////////////////////////////////////
+
+function Parser(text, parseStrings){
+ this.text = text;
+ this.tokens = new Lexer(text, parseStrings).parse();
+ this.index = 0;
+}
+
+Parser.ZERO = function(){
+ return 0;
+};
+
+Parser.prototype = {
+ error: function(msg, token) {
+ throw "Token '" + token.text +
+ "' is " + msg + " at column='" +
+ (token.index + 1) + "' of expression '" +
+ this.text + "' starting at '" + this.text.substring(token.index) + "'.";
+ },
+
+ peekToken: function() {
+ if (this.tokens.length === 0)
+ throw "Unexpected end of expression: " + this.text;
+ return this.tokens[0];
+ },
+
+ peek: function(e1, e2, e3, e4) {
+ var tokens = this.tokens;
+ if (tokens.length > 0) {
+ var token = tokens[0];
+ var t = token.text;
+ if (t==e1 || t==e2 || t==e3 || t==e4 ||
+ (!e1 && !e2 && !e3 && !e4)) {
+ return token;
+ }
+ }
+ return false;
+ },
+
+ expect: function(e1, e2, e3, e4){
+ var token = this.peek(e1, e2, e3, e4);
+ if (token) {
+ this.tokens.shift();
+ this.currentToken = token;
+ return token;
+ }
+ return false;
+ },
+
+ consume: function(e1){
+ if (!this.expect(e1)) {
+ var token = this.peek();
+ throw "Expecting '" + e1 + "' at column '" +
+ (token.index+1) + "' in '" +
+ this.text + "' got '" +
+ this.text.substring(token.index) + "'.";
+ }
+ },
+
+ _unary: function(fn, right) {
+ return function(self) {
+ return fn(self, right(self));
+ };
+ },
+
+ _binary: function(left, fn, right) {
+ return function(self) {
+ return fn(self, left(self), right(self));
+ };
+ },
+
+ hasTokens: function () {
+ return this.tokens.length > 0;
+ },
+
+ assertAllConsumed: function(){
+ if (this.tokens.length !== 0) {
+ throw "Did not understand '" + this.text.substring(this.tokens[0].index) +
+ "' while evaluating '" + this.text + "'.";
+ }
+ },
+
+ statements: function(){
+ var statements = [];
+ while(true) {
+ if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
+ statements.push(this.filterChain());
+ if (!this.expect(';')) {
+ return function (self){
+ var value;
+ for ( var i = 0; i < statements.length; i++) {
+ var statement = statements[i];
+ if (statement)
+ value = statement(self);
+ }
+ return value;
+ };
+ }
+ }
+ },
+
+ filterChain: function(){
+ var left = this.expression();
+ var token;
+ while(true) {
+ if ((token = this.expect('|'))) {
+ left = this._binary(left, token.fn, this.filter());
+ } else {
+ return left;
+ }
+ }
+ },
+
+ filter: function(){
+ return this._pipeFunction(angularFilter);
+ },
+
+ validator: function(){
+ return this._pipeFunction(angularValidator);
+ },
+
+ _pipeFunction: function(fnScope){
+ var fn = this.functionIdent(fnScope);
+ var argsFn = [];
+ var token;
+ while(true) {
+ if ((token = this.expect(':'))) {
+ argsFn.push(this.expression());
+ } else {
+ var fnInvoke = function(self, input){
+ var args = [input];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self));
+ }
+ return fn.apply(self, args);
+ };
+ return function(){
+ return fnInvoke;
+ };
+ }
+ }
+ },
+
+ expression: function(){
+ return this.throwStmt();
+ },
+
+ throwStmt: function(){
+ if (this.expect('throw')) {
+ var throwExp = this.assignment();
+ return function (self) {
+ throw throwExp(self);
+ };
+ } else {
+ return this.assignment();
+ }
+ },
+
+ assignment: function(){
+ var left = this.logicalOR();
+ var token;
+ if (token = this.expect('=')) {
+ if (!left.isAssignable) {
+ throw "Left hand side '" +
+ this.text.substring(0, token.index) + "' of assignment '" +
+ this.text.substring(token.index) + "' is not assignable.";
+ }
+ var ident = function(){return left.isAssignable;};
+ return this._binary(ident, token.fn, this.logicalOR());
+ } else {
+ return left;
+ }
+ },
+
+ logicalOR: function(){
+ var left = this.logicalAND();
+ var token;
+ while(true) {
+ if ((token = this.expect('||'))) {
+ left = this._binary(left, token.fn, this.logicalAND());
+ } else {
+ return left;
+ }
+ }
+ },
+
+ logicalAND: function(){
+ var left = this.equality();
+ var token;
+ if ((token = this.expect('&&'))) {
+ left = this._binary(left, token.fn, this.logicalAND());
+ }
+ return left;
+ },
+
+ equality: function(){
+ var left = this.relational();
+ var token;
+ if ((token = this.expect('==','!='))) {
+ left = this._binary(left, token.fn, this.equality());
+ }
+ return left;
+ },
+
+ relational: function(){
+ var left = this.additive();
+ var token;
+ if (token = this.expect('<', '>', '<=', '>=')) {
+ left = this._binary(left, token.fn, this.relational());
+ }
+ return left;
+ },
+
+ additive: function(){
+ var left = this.multiplicative();
+ var token;
+ while(token = this.expect('+','-')) {
+ left = this._binary(left, token.fn, this.multiplicative());
+ }
+ return left;
+ },
+
+ multiplicative: function(){
+ var left = this.unary();
+ var token;
+ while(token = this.expect('*','/','%')) {
+ left = this._binary(left, token.fn, this.unary());
+ }
+ return left;
+ },
+
+ unary: function(){
+ var token;
+ if (this.expect('+')) {
+ return this.primary();
+ } else if (token = this.expect('-')) {
+ return this._binary(Parser.ZERO, token.fn, this.unary());
+ } else if (token = this.expect('!')) {
+ return this._unary(token.fn, this.unary());
+ } else {
+ return this.primary();
+ }
+ },
+
+ functionIdent: function(fnScope) {
+ var token = this.expect();
+ var element = token.text.split('.');
+ var instance = fnScope;
+ var key;
+ for ( var i = 0; i < element.length; i++) {
+ key = element[i];
+ if (instance)
+ instance = instance[key];
+ }
+ if (typeof instance != 'function') {
+ throw "Function '" + token.text + "' at column '" +
+ (token.index+1) + "' in '" + this.text + "' is not defined.";
+ }
+ return instance;
+ },
+
+ primary: function() {
+ var primary;
+ if (this.expect('(')) {
+ var expression = this.filterChain();
+ this.consume(')');
+ primary = expression;
+ } else if (this.expect('[')) {
+ primary = this.arrayDeclaration();
+ } else if (this.expect('{')) {
+ primary = this.object();
+ } else if (this.expect('{:')) {
+ primary = this.closure(false);
+ } else if (this.expect('{(')) {
+ primary = this.closure(true);
+ } else {
+ var token = this.expect();
+ primary = token.fn;
+ if (!primary) {
+ this.error("not a primary expression", token);
+ }
+ }
+ var next;
+ while (next = this.expect('(', '[', '.')) {
+ if (next.text === '(') {
+ primary = this.functionCall(primary);
+ } else if (next.text === '[') {
+ primary = this.objectIndex(primary);
+ } else if (next.text === '.') {
+ primary = this.fieldAccess(primary);
+ } else {
+ throw "IMPOSSIBLE";
+ }
+ }
+ return primary;
+ },
+
+ closure: function(hasArgs) {
+ var args = [];
+ if (hasArgs) {
+ if (!this.expect(')')) {
+ args.push(this.expect().text);
+ while(this.expect(',')) {
+ args.push(this.expect().text);
+ }
+ this.consume(')');
+ }
+ this.consume(":");
+ }
+ var statements = this.statements();
+ this.consume("}");
+ return function(self) {
+ return function($){
+ var scope = createScope(self);
+ scope['$'] = $;
+ for ( var i = 0; i < args.length; i++) {
+ setter(scope, args[i], arguments[i]);
+ }
+ return statements(scope);
+ };
+ };
+ },
+
+ fieldAccess: function(object) {
+ var field = this.expect().text;
+ var getter = getterFn(field);
+ var fn = function (self){
+ return getter(object(self));
+ };
+ fn.isAssignable = field;
+ return fn;
+ },
+
+ objectIndex: function(obj) {
+ var indexFn = this.expression();
+ this.consume(']');
+ if (this.expect('=')) {
+ var rhs = this.expression();
+ return function (self){
+ return obj(self)[indexFn(self)] = rhs(self);
+ };
+ } else {
+ return function (self){
+ var o = obj(self);
+ var i = indexFn(self);
+ return (o) ? o[i] : undefined;
+ };
+ }
+ },
+
+ functionCall: function(fn) {
+ var argsFn = [];
+ if (this.peekToken().text != ')') {
+ do {
+ argsFn.push(this.expression());
+ } while (this.expect(','));
+ }
+ this.consume(')');
+ return function (self){
+ var args = [];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self));
+ }
+ var fnPtr = fn(self);
+ if (typeof fnPtr === 'function') {
+ return fnPtr.apply(self, args);
+ } else {
+ throw "Expression '" + fn.isAssignable + "' is not a function.";
+ }
+ };
+ },
+
+ // This is used with json array declaration
+ arrayDeclaration: function () {
+ var elementFns = [];
+ if (this.peekToken().text != ']') {
+ do {
+ elementFns.push(this.expression());
+ } while (this.expect(','));
+ }
+ this.consume(']');
+ return function (self){
+ var array = [];
+ for ( var i = 0; i < elementFns.length; i++) {
+ array.push(elementFns[i](self));
+ }
+ return array;
+ };
+ },
+
+ object: function () {
+ var keyValues = [];
+ if (this.peekToken().text != '}') {
+ do {
+ var token = this.expect(),
+ key = token.string || token.text;
+ this.consume(":");
+ var value = this.expression();
+ keyValues.push({key:key, value:value});
+ } while (this.expect(','));
+ }
+ this.consume('}');
+ return function (self){
+ var object = {};
+ for ( var i = 0; i < keyValues.length; i++) {
+ var keyValue = keyValues[i];
+ var value = keyValue.value(self);
+ object[keyValue.key] = value;
+ }
+ return object;
+ };
+ },
+
+ entityDeclaration: function () {
+ var decl = [];
+ while(this.hasTokens()) {
+ decl.push(this.entityDecl());
+ if (!this.expect(';')) {
+ this.assertAllConsumed();
+ }
+ }
+ return function (self){
+ var code = "";
+ for ( var i = 0; i < decl.length; i++) {
+ code += decl[i](self);
+ }
+ return code;
+ };
+ },
+
+ entityDecl: function () {
+ var entity = this.expect().text;
+ var instance;
+ var defaults;
+ if (this.expect('=')) {
+ instance = entity;
+ entity = this.expect().text;
+ }
+ if (this.expect(':')) {
+ defaults = this.primary()(null);
+ }
+ return function(self) {
+ var Entity = self.datastore.entity(entity, defaults);
+ setter(self, entity, Entity);
+ if (instance) {
+ var document = Entity();
+ document['$$anchor'] = instance;
+ setter(self, instance, document);
+ return "$anchor." + instance + ":{" +
+ instance + "=" + entity + ".load($anchor." + instance + ");" +
+ instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" +
+ "};";
+ } else {
+ return "";
+ }
+ };
+ },
+
+ watch: function () {
+ var decl = [];
+ while(this.hasTokens()) {
+ decl.push(this.watchDecl());
+ if (!this.expect(';')) {
+ this.assertAllConsumed();
+ }
+ }
+ this.assertAllConsumed();
+ return function (self){
+ for ( var i = 0; i < decl.length; i++) {
+ var d = decl[i](self);
+ self.addListener(d.name, d.fn);
+ }
+ };
+ },
+
+ watchDecl: function () {
+ var anchorName = this.expect().text;
+ this.consume(":");
+ var expression;
+ if (this.peekToken().text == '{') {
+ this.consume("{");
+ expression = this.statements();
+ this.consume("}");
+ } else {
+ expression = this.expression();
+ }
+ return function(self) {
+ return {name:anchorName, fn:expression};
+ };
+ }
+};
+
diff --git a/src/Resource.js b/src/Resource.js
new file mode 100644
index 00000000..ba460c30
--- /dev/null
+++ b/src/Resource.js
@@ -0,0 +1,140 @@
+function Route(template, defaults) {
+ this.template = template = template + '#';
+ this.defaults = defaults || {};
+ var urlParams = this.urlParams = {};
+ foreach(template.split(/\W/), function(param){
+ if (param && template.match(new RegExp(":" + param + "\\W"))) {
+ urlParams[param] = true;
+ }
+ });
+}
+
+Route.prototype = {
+ url: function(params) {
+ var path = [];
+ var self = this;
+ var url = this.template;
+ params = params || {};
+ foreach(this.urlParams, function(_, urlParam){
+ var value = params[urlParam] || self.defaults[urlParam] || "";
+ url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1");
+ });
+ url = url.replace(/\/?#$/, '');
+ var query = [];
+ foreachSorted(params, function(value, key){
+ if (!self.urlParams[key]) {
+ query.push(encodeURI(key) + '=' + encodeURI(value));
+ }
+ });
+ return url + (query.length ? '?' + query.join('&') : '');
+ }
+};
+
+function ResourceFactory(xhr) {
+ this.xhr = xhr;
+}
+
+ResourceFactory.DEFAULT_ACTIONS = {
+ 'get': {method:'GET'},
+ 'save': {method:'POST'},
+ 'query': {method:'GET', isArray:true},
+ 'remove': {method:'DELETE'},
+ 'delete': {method:'DELETE'}
+};
+
+ResourceFactory.prototype = {
+ route: function(url, paramDefaults, actions){
+ var self = this;
+ var route = new Route(url);
+ actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions);
+ function extractParams(data){
+ var ids = {};
+ foreach(paramDefaults || {}, function(value, key){
+ ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
+ });
+ return ids;
+ }
+
+ function Resource(value){
+ copy(value || {}, this);
+ }
+
+ foreach(actions, function(action, name){
+ var isGet = action.method == 'GET';
+ var isPost = action.method == 'POST';
+ Resource[name] = function (a1, a2, a3) {
+ var params = {};
+ var data;
+ var callback = noop;
+ switch(arguments.length) {
+ case 3: callback = a3;
+ case 2:
+ if (isFunction(a2)) {
+ callback = a2;
+ } else {
+ params = a1;
+ data = a2;
+ break;
+ }
+ case 1:
+ if (isFunction(a1)) callback = a1;
+ else if (isPost) data = a1;
+ else params = a1;
+ break;
+ case 0: break;
+ default:
+ throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments.";
+ }
+
+ var value = action.isArray ? [] : new Resource(data;)
+ self.xhr(
+ action.method,
+ route.url(extend({}, action.params || {}, extractParams(data), params)),
+ data,
+ function(status, response, clear) {
+ if (status == 200) {
+ if (action.isArray) {
+ if (action.cacheThenRetrieve)
+ value = [];
+ foreach(response, function(item){
+ value.push(new Resource(item));
+ });
+ } else {
+ copy(response, value);
+ }
+ (callback||noop)(value);
+ } else {
+ throw {status: status, response:response, message: status + ": " + response};
+ }
+ },
+ action.cacheThenRetrieve
+ );
+ return value;
+ };
+
+ Resource.bind = function(additionalParamDefaults){
+ return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions);
+ };
+
+ if (!isGet) {
+ Resource.prototype['$' + name] = function(a1, a2){
+ var params = {};
+ var callback = noop;
+ switch(arguments.length) {
+ case 2: params = a1; callback = a2;
+ case 1: if (typeof a1 == 'function') callback = a1; else params = a1;
+ case 0: break;
+ default:
+ throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments.";
+ }
+ var self = this;
+ Resource[name](params, this, function(response){
+ copy(response, self);
+ callback(self);
+ });
+ };
+ }
+ });
+ return Resource;
+ }
+};
diff --git a/src/Scope.js b/src/Scope.js
new file mode 100644
index 00000000..637fc25e
--- /dev/null
+++ b/src/Scope.js
@@ -0,0 +1,224 @@
+function getter(instance, path, unboundFn) {
+ if (!path) return instance;
+ var element = path.split('.');
+ var key;
+ var lastInstance = instance;
+ var len = element.length;
+ for ( var i = 0; i < len; i++) {
+ key = element[i];
+ if (!key.match(/^[\$\w][\$\w\d]*$/))
+ throw "Expression '" + path + "' is not a valid expression for accesing variables.";
+ if (instance) {
+ lastInstance = instance;
+ instance = instance[key];
+ }
+ if (isUndefined(instance) && key.charAt(0) == '$') {
+ var type = angular['Global']['typeOf'](lastInstance);
+ type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
+ var fn = type ? type[[key.substring(1)]] : undefined;
+ if (fn) {
+ instance = bind(lastInstance, fn, lastInstance);
+ return instance;
+ }
+ }
+ }
+ if (!unboundFn && isFunction(instance) && !instance['$$factory']) {
+ return bind(lastInstance, instance);
+ }
+ return instance;
+}
+
+function setter(instance, path, value){
+ var element = path.split('.');
+ for ( var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var newInstance = instance[key];
+ if (!newInstance) {
+ newInstance = {};
+ instance[key] = newInstance;
+ }
+ instance = newInstance;
+ }
+ instance[element.shift()] = value;
+ return value;
+}
+
+///////////////////////////////////
+
+var getterFnCache = {};
+function getterFn(path){
+ var fn = getterFnCache[path];
+ if (fn) return fn;
+
+ var code = 'function (self){\n';
+ code += ' var last, fn, type;\n';
+ foreach(path.split('.'), function(key) {
+ key = (key == 'this') ? '["this"]' : '.' + key;
+ code += ' if(!self) return self;\n';
+ code += ' last = self;\n';
+ code += ' self = self' + key + ';\n';
+ code += ' if(typeof self == "function") \n';
+ code += ' self = function(){ return last'+key+'.apply(last, arguments); };\n';
+ if (key.charAt(1) == '$') {
+ // special code for super-imposed functions
+ var name = key.substr(2);
+ code += ' if(!self) {\n';
+ code += ' type = angular.Global.typeOf(last);\n';
+ code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n';
+ code += ' if (fn)\n';
+ code += ' self = function(){ return fn.apply(last, [last].concat(slice.call(arguments, 0, arguments.length))); };\n';
+ code += ' }\n';
+ }
+ });
+ code += ' return self;\n}';
+ fn = eval('(' + code + ')');
+ fn.toString = function(){ return code; };
+
+ return getterFnCache[path] = fn;
+}
+
+///////////////////////////////////
+
+var compileCache = {};
+function expressionCompile(exp){
+ if (isFunction(exp)) return exp;
+ var fn = compileCache[exp];
+ if (!fn) {
+ var parser = new Parser(exp);
+ var fnSelf = parser.statements();
+ parser.assertAllConsumed();
+ fn = compileCache[exp] = extend(
+ function(){ return fnSelf(this);},
+ {fnSelf: fnSelf});
+ }
+ return fn;
+}
+
+function rethrow(e) { throw e; }
+function errorHandlerFor(element, error) {
+ elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error);
+}
+
+var scopeId = 0;
+function createScope(parent, services, existing) {
+ function Parent(){}
+ function API(){}
+ function Behavior(){}
+
+ var instance, behavior, api, evalLists = {sorted:[]}, servicesCache = extend({}, existing);
+
+ parent = Parent.prototype = (parent || {});
+ api = API.prototype = new Parent();
+ behavior = Behavior.prototype = new API();
+ instance = new Behavior();
+
+ extend(api, {
+ 'this': instance,
+ $id: (scopeId++),
+ $parent: parent,
+ $bind: bind(instance, bind, instance),
+ $get: bind(instance, getter, instance),
+ $set: bind(instance, setter, instance),
+
+ $eval: function $eval(exp) {
+ if (exp !== undefined) {
+ return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
+ } else {
+ for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) {
+ for ( var queue = evalLists.sorted[i],
+ jSize = queue.length,
+ j= 0; j < jSize; j++) {
+ instance.$tryEval(queue[j].fn, queue[j].handler);
+ }
+ }
+ }
+ },
+
+ $tryEval: function (expression, exceptionHandler) {
+ try {
+ return expressionCompile(expression).apply(instance, slice.call(arguments, 2, arguments.length));
+ } catch (e) {
+ error(e);
+ if (isFunction(exceptionHandler)) {
+ exceptionHandler(e);
+ } else if (exceptionHandler) {
+ errorHandlerFor(exceptionHandler, e);
+ } else if (isFunction(instance.$exceptionHandler)) {
+ instance.$exceptionHandler(e);
+ }
+ }
+ },
+
+ $watch: function(watchExp, listener, exceptionHandler) {
+ var watch = expressionCompile(watchExp),
+ last;
+ function watcher(){
+ var value = watch.call(instance),
+ lastValue = last;
+ if (last !== value) {
+ last = value;
+ instance.$tryEval(listener, exceptionHandler, value, lastValue);
+ }
+ }
+ instance.$onEval(PRIORITY_WATCH, watcher);
+ watcher();
+ },
+
+ $onEval: function(priority, expr, exceptionHandler){
+ if (!isNumber(priority)) {
+ exceptionHandler = expr;
+ expr = priority;
+ priority = 0;
+ }
+ var evalList = evalLists[priority];
+ if (!evalList) {
+ evalList = evalLists[priority] = [];
+ evalList.priority = priority;
+ evalLists.sorted.push(evalList);
+ evalLists.sorted.sort(function(a,b){return a.priority-b.priority;});
+ }
+ evalList.push({
+ fn: expressionCompile(expr),
+ handler: exceptionHandler
+ });
+ },
+
+ $become: function(Class) {
+ // remove existing
+ foreach(behavior, function(value, key){ delete behavior[key]; });
+ foreach((Class || noop).prototype, function(fn, name){
+ behavior[name] = bind(instance, fn);
+ });
+ (Class || noop).call(instance);
+ }
+
+ });
+
+ if (!parent.$root) {
+ api.$root = instance;
+ api.$parent = instance;
+ }
+
+ function inject(name){
+ var service = servicesCache[name], factory, args = [];
+ if (isUndefined(service)) {
+ factory = services[name];
+ if (!isFunction(factory))
+ throw "Don't know how to inject '" + name + "'.";
+ foreach(factory.inject, function(dependency){
+ args.push(inject(dependency));
+ });
+ servicesCache[name] = service = factory.apply(instance, args);
+ }
+ return service;
+ }
+
+ foreach(services, function(_, name){
+ var service = inject(name);
+ if (service) {
+ setter(instance, name, service);
+ }
+ });
+
+ return instance;
+}
diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js
new file mode 100644
index 00000000..90e1104e
--- /dev/null
+++ b/src/angular-bootstrap.js
@@ -0,0 +1,70 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * 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.
+ */
+(function(previousOnLoad){
+ var filename = /(.*)\/angular-(.*).js(#(.*))?/,
+ scripts = document.getElementsByTagName("SCRIPT"),
+ serverPath,
+ config,
+ match;
+ for(var j = 0; j < scripts.length; j++) {
+ match = (scripts[j].src || "").match(filename);
+ if (match) {
+ serverPath = match[1];
+ config = match[4];
+ }
+ }
+
+ function addScript(file){
+ document.write('<script type="text/javascript" src="' + serverPath + file +'"></script>');
+ }
+
+ addScript("/Angular.js");
+ addScript("/JSON.js");
+ addScript("/Compiler.js");
+ addScript("/Scope.js");
+ addScript("/jqLite.js");
+ addScript("/Parser.js");
+ addScript("/Resource.js");
+ addScript("/Browser.js");
+ addScript("/AngularPublic.js");
+
+ // Extension points
+ addScript("/services.js");
+ addScript("/apis.js");
+ addScript("/filters.js");
+ addScript("/formatters.js");
+ addScript("/validators.js");
+ addScript("/directives.js");
+ addScript("/markups.js");
+ addScript("/widgets.js");
+
+ window.onload = function(){
+ try {
+ if (previousOnLoad) previousOnLoad();
+ } catch(e) {}
+ angularInit(parseKeyValue(config));
+ };
+
+})(window.onload);
+
diff --git a/src/angular.prefix b/src/angular.prefix
new file mode 100644
index 00000000..a1b4e151
--- /dev/null
+++ b/src/angular.prefix
@@ -0,0 +1,24 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * 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.
+ */
+(function(window, document, previousOnLoad){
diff --git a/src/angular.suffix b/src/angular.suffix
new file mode 100644
index 00000000..36d73df2
--- /dev/null
+++ b/src/angular.suffix
@@ -0,0 +1,9 @@
+
+ window.onload = function(){
+ try {
+ if (previousOnLoad) previousOnLoad();
+ } catch(e) {}
+ angularInit(parseKeyValue(angularJsConfig(document)));
+ };
+
+})(window, document, window.onload);
diff --git a/src/apis.js b/src/apis.js
new file mode 100644
index 00000000..306d0ce8
--- /dev/null
+++ b/src/apis.js
@@ -0,0 +1,338 @@
+var angularGlobal = {
+ 'typeOf':function(obj){
+ if (obj === null) return "null";
+ var type = typeof obj;
+ if (type == "object") {
+ if (obj instanceof Array) return "array";
+ if (obj instanceof Date) return "date";
+ if (obj.nodeType == 1) return "element";
+ }
+ return type;
+ }
+};
+
+var angularCollection = {
+ 'size': size
+};
+var angularObject = {
+ 'extend': extend
+};
+var angularArray = {
+ 'indexOf': indexOf,
+ 'include': includes,
+ 'includeIf':function(array, value, condition) {
+ var index = indexOf(array, value);
+ if (condition) {
+ if (index == -1)
+ array.push(value);
+ } else {
+ array.splice(index, 1);
+ }
+ return array;
+ },
+ 'sum':function(array, expression) {
+ var fn = angular['Function']['compile'](expression);
+ var sum = 0;
+ for (var i = 0; i < array.length; i++) {
+ var value = 1 * fn(array[i]);
+ if (!isNaN(value)){
+ sum += value;
+ }
+ }
+ return sum;
+ },
+ 'remove':function(array, value) {
+ var index = indexOf(array, value);
+ if (index >=0)
+ array.splice(index, 1);
+ return value;
+ },
+ 'find':function(array, condition, defaultValue) {
+ if (!condition) return undefined;
+ var fn = angular['Function']['compile'](condition);
+ foreach(array, function($){
+ if (fn($)){
+ defaultValue = $;
+ return true;
+ }
+ });
+ return defaultValue;
+ },
+ 'findById':function(array, id) {
+ return angular.Array.find(array, function($){return $.$id == id;}, null);
+ },
+ 'filter':function(array, expression) {
+ var predicates = [];
+ predicates.check = function(value) {
+ for (var j = 0; j < predicates.length; j++) {
+ if(!predicates[j](value)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ var search = function(obj, text){
+ if (text.charAt(0) === '!') {
+ return !search(obj, text.substr(1));
+ }
+ switch (typeof obj) {
+ case "boolean":
+ case "number":
+ case "string":
+ return ('' + obj).toLowerCase().indexOf(text) > -1;
+ case "object":
+ for ( var objKey in obj) {
+ if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
+ return true;
+ }
+ }
+ return false;
+ case "array":
+ for ( var i = 0; i < obj.length; i++) {
+ if (search(obj[i], text)) {
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+ };
+ switch (typeof expression) {
+ case "boolean":
+ case "number":
+ case "string":
+ expression = {$:expression};
+ case "object":
+ for (var key in expression) {
+ if (key == '$') {
+ (function(){
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(value, text);
+ });
+ })();
+ } else {
+ (function(){
+ var path = key;
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(getter(value, path), text);
+ });
+ })();
+ }
+ }
+ break;
+ case "function":
+ predicates.push(expression);
+ break;
+ default:
+ return array;
+ }
+ var filtered = [];
+ for ( var j = 0; j < array.length; j++) {
+ var value = array[j];
+ if (predicates.check(value)) {
+ filtered.push(value);
+ }
+ }
+ return filtered;
+ },
+ 'add':function(array, value) {
+ array.push(isUndefined(value)? {} : value);
+ return array;
+ },
+ 'count':function(array, condition) {
+ if (!condition) return array.length;
+ var fn = angular['Function']['compile'](condition), count = 0;
+ foreach(array, function(value){
+ if (fn(value)) {
+ count ++;
+ }
+ });
+ return count;
+ },
+ 'orderBy':function(array, expression, descend) {
+ function reverse(comp, descending) {
+ return toBoolean(descending) ?
+ function(a,b){return comp(b,a);} : comp;
+ }
+ function compare(v1, v2){
+ var t1 = typeof v1;
+ var t2 = typeof v2;
+ if (t1 == t2) {
+ if (t1 == "string") v1 = v1.toLowerCase();
+ if (t1 == "string") v2 = v2.toLowerCase();
+ if (v1 === v2) return 0;
+ return v1 < v2 ? -1 : 1;
+ } else {
+ return t1 < t2 ? -1 : 1;
+ }
+ }
+ expression = isArray(expression) ? expression: [expression];
+ expression = map(expression, function($){
+ var descending = false;
+ if (typeof $ == "string" && ($.charAt(0) == '+' || $.charAt(0) == '-')) {
+ descending = $.charAt(0) == '-';
+ $ = $.substring(1);
+ }
+ var get = $ ? expressionCompile($).fnSelf : identity;
+ return reverse(function(a,b){
+ return compare(get(a),get(b));
+ }, descending);
+ });
+ var comparator = function(o1, o2){
+ for ( var i = 0; i < expression.length; i++) {
+ var comp = expression[i](o1, o2);
+ if (comp !== 0) return comp;
+ }
+ return 0;
+ };
+ var arrayCopy = [];
+ for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
+ return arrayCopy.sort(reverse(comparator, descend));
+ },
+ 'orderByToggle':function(predicate, attribute) {
+ var STRIP = /^([+|-])?(.*)/;
+ var ascending = false;
+ var index = -1;
+ foreach(predicate, function($, i){
+ if (index == -1) {
+ if ($ == attribute) {
+ ascending = true;
+ index = i;
+ return true;
+ }
+ if (($.charAt(0)=='+'||$.charAt(0)=='-') && $.substring(1) == attribute) {
+ ascending = $.charAt(0) == '+';
+ index = i;
+ return true;
+ }
+ }
+ });
+ if (index >= 0) {
+ predicate.splice(index, 1);
+ }
+ predicate.unshift((ascending ? "-" : "+") + attribute);
+ return predicate;
+ },
+ 'orderByDirection':function(predicate, attribute, ascend, descend) {
+ ascend = ascend || 'ng-ascend';
+ descend = descend || 'ng-descend';
+ var att = predicate[0] || '';
+ var direction = true;
+ if (att.charAt(0) == '-') {
+ att = att.substring(1);
+ direction = false;
+ } else if(att.charAt(0) == '+') {
+ att = att.substring(1);
+ }
+ return att == attribute ? (direction ? ascend : descend) : "";
+ },
+ 'merge':function(array, index, mergeValue) {
+ var value = array[index];
+ if (!value) {
+ value = {};
+ array[index] = value;
+ }
+ merge(mergeValue, value);
+ return array;
+ }
+};
+
+var angularString = {
+ 'quote':function(string) {
+ return '"' + string.replace(/\\/g, '\\\\').
+ replace(/"/g, '\\"').
+ replace(/\n/g, '\\n').
+ replace(/\f/g, '\\f').
+ replace(/\r/g, '\\r').
+ replace(/\t/g, '\\t').
+ replace(/\v/g, '\\v') +
+ '"';
+ },
+ 'quoteUnicode':function(string) {
+ var str = angular['String']['quote'](string);
+ var chars = [];
+ for ( var i = 0; i < str.length; i++) {
+ var ch = str.charCodeAt(i);
+ if (ch < 128) {
+ chars.push(str.charAt(i));
+ } else {
+ var encode = "000" + ch.toString(16);
+ chars.push("\\u" + encode.substring(encode.length - 4));
+ }
+ }
+ return chars.join('');
+ },
+ 'toDate':function(string){
+ var match;
+ if (typeof string == 'string' &&
+ (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){
+ var date = new Date(0);
+ date.setUTCFullYear(match[1], match[2] - 1, match[3]);
+ date.setUTCHours(match[4], match[5], match[6], 0);
+ return date;
+ }
+ return string;
+ }
+};
+
+var angularDate = {
+ 'toString':function(date){
+ function pad(n) { return n < 10 ? "0" + n : n; }
+ return !date ? date :
+ date.getUTCFullYear() + '-' +
+ pad(date.getUTCMonth() + 1) + '-' +
+ pad(date.getUTCDate()) + 'T' +
+ pad(date.getUTCHours()) + ':' +
+ pad(date.getUTCMinutes()) + ':' +
+ pad(date.getUTCSeconds()) + 'Z' ;
+ }
+ };
+
+var angularFunction = {
+ 'compile':function(expression) {
+ if (isFunction(expression)){
+ return expression;
+ } else if (expression){
+ return expressionCompile(expression).fnSelf;
+ } else {
+ return identity;
+ }
+ }
+};
+
+function defineApi(dst, chain, underscoreNames){
+ if (_) {
+ var lastChain = _.last(chain);
+ foreach(underscoreNames, function(name){
+ lastChain[name] = _[name];
+ });
+ }
+ angular[dst] = angular[dst] || {};
+ foreach(chain, function(parent){
+ extend(angular[dst], parent);
+ });
+}
+defineApi('Global', [angularGlobal],
+ ['extend', 'clone','isEqual',
+ 'isElement', 'isArray', 'isFunction', 'isUndefined']);
+defineApi('Collection', [angularGlobal, angularCollection],
+ ['each', 'map', 'reduce', 'reduceRight', 'detect',
+ 'select', 'reject', 'all', 'any', 'include',
+ 'invoke', 'pluck', 'max', 'min', 'sortBy',
+ 'sortedIndex', 'toArray', 'size']);
+defineApi('Array', [angularGlobal, angularCollection, angularArray],
+ ['first', 'last', 'compact', 'flatten', 'without',
+ 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']);
+defineApi('Object', [angularGlobal, angularCollection, angularObject],
+ ['keys', 'values']);
+defineApi('String', [angularGlobal, angularString], []);
+defineApi('Date', [angularGlobal, angularDate], []);
+//IE bug
+angular['Date']['toString'] = angularDate['toString'];
+defineApi('Function', [angularGlobal, angularCollection, angularFunction],
+ ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']);
diff --git a/src/delete/Binder.js b/src/delete/Binder.js
new file mode 100644
index 00000000..9fc32513
--- /dev/null
+++ b/src/delete/Binder.js
@@ -0,0 +1,356 @@
+function Binder(doc, widgetFactory, datastore, location, config) {
+ this.doc = doc;
+ this.location = location;
+ this.datastore = datastore;
+ this.anchor = {};
+ this.widgetFactory = widgetFactory;
+ this.config = config || {};
+ this.updateListeners = [];
+}
+
+Binder.parseBindings = function(string) {
+ var results = [];
+ var lastIndex = 0;
+ var index;
+ while((index = string.indexOf('{{', lastIndex)) > -1) {
+ if (lastIndex < index)
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+
+ index = string.indexOf('}}', index);
+ index = index < 0 ? string.length : index + 2;
+
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+ }
+ if (lastIndex != string.length)
+ results.push(string.substr(lastIndex, string.length - lastIndex));
+ return results.length === 0 ? [ string ] : results;
+};
+
+Binder.hasBinding = function(string) {
+ var bindings = Binder.parseBindings(string);
+ return bindings.length > 1 || Binder.binding(bindings[0]) !== null;
+};
+
+Binder.binding = function(string) {
+ var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
+ return binding ? binding[1] : null;
+};
+
+
+Binder.prototype = {
+ parseQueryString: function(query) {
+ var params = {};
+ query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,
+ function (match, left, right) {
+ if (left) params[decodeURIComponent(left)] = decodeURIComponent(right);
+ });
+ return params;
+ },
+
+ parseAnchor: function() {
+ var self = this, url = this.location['get']() || "";
+
+ var anchorIndex = url.indexOf('#');
+ if (anchorIndex < 0) return;
+ var anchor = url.substring(anchorIndex + 1);
+
+ var anchorQuery = this.parseQueryString(anchor);
+ foreach(self.anchor, function(newValue, key) {
+ delete self.anchor[key];
+ });
+ foreach(anchorQuery, function(newValue, key) {
+ self.anchor[key] = newValue;
+ });
+ },
+
+ onUrlChange: function() {
+ this.parseAnchor();
+ this.updateView();
+ },
+
+ updateAnchor: function() {
+ var url = this.location['get']() || "";
+ var anchorIndex = url.indexOf('#');
+ if (anchorIndex > -1)
+ url = url.substring(0, anchorIndex);
+ url += "#";
+ var sep = '';
+ for (var key in this.anchor) {
+ var value = this.anchor[key];
+ if (typeof value === 'undefined' || value === null) {
+ delete this.anchor[key];
+ } else {
+ url += sep + encodeURIComponent(key);
+ if (value !== true)
+ url += "=" + encodeURIComponent(value);
+ sep = '&';
+ }
+ }
+ this.location['set'](url);
+ return url;
+ },
+
+ updateView: function() {
+ var start = new Date().getTime();
+ var scope = jQuery(this.doc).scope();
+ scope.clearInvalid();
+ scope.updateView();
+ var end = new Date().getTime();
+ this.updateAnchor();
+ foreach(this.updateListeners, function(fn) {fn();});
+ },
+
+ docFindWithSelf: function(exp){
+ var doc = jQuery(this.doc);
+ var selection = doc.find(exp);
+ if (doc.is(exp)){
+ selection = selection.andSelf();
+ }
+ return selection;
+ },
+
+ executeInit: function() {
+ this.docFindWithSelf("[ng-init]").each(function() {
+ var jThis = jQuery(this);
+ var scope = jThis.scope();
+ try {
+ scope.eval(jThis.attr('ng-init'));
+ } catch (e) {
+ alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true));
+ }
+ });
+ },
+
+ entity: function (scope) {
+ var self = this;
+ this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() {
+ try {
+ var jNode = jQuery(this);
+ var decl = scope.entity(jNode.attr("ng-entity"), self.datastore);
+ return decl + (jNode.attr('ng-watch') || "");
+ } catch (e) {
+ log(e);
+ alert(e);
+ }
+ });
+ },
+
+ compile: function() {
+ var jNode = jQuery(this.doc);
+ if (this.config['autoSubmit']) {
+ var submits = this.docFindWithSelf(":submit").not("[ng-action]");
+ submits.attr("ng-action", "$save()");
+ submits.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", '{disabled:"{{$invalidWidgets}}"}');
+ }
+ this.precompile(this.doc)(this.doc, jNode.scope(), "");
+ this.docFindWithSelf("a[ng-action]").live('click', function (event) {
+ var jNode = jQuery(this);
+ var scope = jNode.scope();
+ try {
+ scope.eval(jNode.attr('ng-action'));
+ jNode.removeAttr('ng-error');
+ jNode.removeClass("ng-exception");
+ } catch (e) {
+ jNode.addClass("ng-exception");
+ jNode.attr('ng-error', toJson(e, true));
+ }
+ scope.get('$updateView')();
+ return false;
+ });
+ },
+
+ translateBinding: function(node, parentPath, factories) {
+ var path = parentPath.concat();
+ var offset = path.pop();
+ var parts = Binder.parseBindings(node.nodeValue);
+ if (parts.length > 1 || Binder.binding(parts[0])) {
+ var parent = node.parentNode;
+ if (isLeafNode(parent)) {
+ parent.setAttribute('ng-bind-template', node.nodeValue);
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ return new BindUpdater(node, node.getAttribute('ng-bind-template'));
+ }});
+ } else {
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ var binding = Binder.binding(part);
+ var newNode;
+ if (binding) {
+ newNode = document.createElement("span");
+ var jNewNode = jQuery(newNode);
+ jNewNode.attr("ng-bind", binding);
+ if (i === 0) {
+ factories.push({path:path.concat(offset + i), fn:this.ng_bind});
+ }
+ } else if (msie && part.charAt(0) == ' ') {
+ newNode = document.createElement("span");
+ newNode.innerHTML = '&nbsp;' + part.substring(1);
+ } else {
+ newNode = document.createTextNode(part);
+ }
+ parent.insertBefore(newNode, node);
+ }
+ }
+ parent.removeChild(node);
+ }
+ },
+
+ precompile: function(root) {
+ var factories = [];
+ this.precompileNode(root, [], factories);
+ return function (template, scope, prefix) {
+ var len = factories.length;
+ for (var i = 0; i < len; i++) {
+ var factory = factories[i];
+ var node = template;
+ var path = factory.path;
+ for (var j = 0; j < path.length; j++) {
+ node = node.childNodes[path[j]];
+ }
+ try {
+ scope.addWidget(factory.fn(node, scope, prefix));
+ } catch (e) {
+ alert(e);
+ }
+ }
+ };
+ },
+
+ precompileNode: function(node, path, factories) {
+ var nodeType = node.nodeType;
+ if (nodeType == Node.TEXT_NODE) {
+ this.translateBinding(node, path, factories);
+ return;
+ } else if (nodeType != Node.ELEMENT_NODE && nodeType != Node.DOCUMENT_NODE) {
+ return;
+ }
+
+ if (!node.getAttribute) return;
+ var nonBindable = node.getAttribute('ng-non-bindable');
+ if (nonBindable || nonBindable === "") return;
+
+ var attributes = node.attributes;
+ if (attributes) {
+ var bindings = node.getAttribute('ng-bind-attr');
+ node.removeAttribute('ng-bind-attr');
+ bindings = bindings ? fromJson(bindings) : {};
+ var attrLen = attributes.length;
+ for (var i = 0; i < attrLen; i++) {
+ var attr = attributes[i];
+ var attrName = attr.name;
+ // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
+ var attrValue = msie && attrName == 'href' ?
+ decodeURI(node.getAttribute(attrName, 2)) : attr.value;
+ if (Binder.hasBinding(attrValue)) {
+ bindings[attrName] = attrValue;
+ }
+ }
+ var json = toJson(bindings);
+ if (json.length > 2) {
+ node.setAttribute("ng-bind-attr", json);
+ }
+ }
+
+ if (!node.getAttribute) log(node);
+ var repeaterExpression = node.getAttribute('ng-repeat');
+ if (repeaterExpression) {
+ node.removeAttribute('ng-repeat');
+ var precompiled = this.precompile(node);
+ var view = document.createComment("ng-repeat: " + repeaterExpression);
+ var parentNode = node.parentNode;
+ parentNode.insertBefore(view, node);
+ parentNode.removeChild(node);
+ function template(childScope, prefix, i) {
+ var clone = jQuery(node).clone();
+ clone.css('display', '');
+ clone.attr('ng-repeat-index', "" + i);
+ clone.data('scope', childScope);
+ precompiled(clone[0], childScope, prefix + i + ":");
+ return clone;
+ }
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ return new RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix);
+ }});
+ return;
+ }
+
+ if (node.getAttribute('ng-eval')) factories.push({path:path, fn:this.ng_eval});
+ if (node.getAttribute('ng-bind')) factories.push({path:path, fn:this.ng_bind});
+ if (node.getAttribute('ng-bind-attr')) factories.push({path:path, fn:this.ng_bind_attr});
+ if (node.getAttribute('ng-hide')) factories.push({path:path, fn:this.ng_hide});
+ if (node.getAttribute('ng-show')) factories.push({path:path, fn:this.ng_show});
+ if (node.getAttribute('ng-class')) factories.push({path:path, fn:this.ng_class});
+ if (node.getAttribute('ng-class-odd')) factories.push({path:path, fn:this.ng_class_odd});
+ if (node.getAttribute('ng-class-even')) factories.push({path:path, fn:this.ng_class_even});
+ if (node.getAttribute('ng-style')) factories.push({path:path, fn:this.ng_style});
+ if (node.getAttribute('ng-watch')) factories.push({path:path, fn:this.ng_watch});
+ var nodeName = node.nodeName;
+ if ((nodeName == 'INPUT' ) ||
+ nodeName == 'TEXTAREA' ||
+ nodeName == 'SELECT' ||
+ nodeName == 'BUTTON') {
+ var self = this;
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ node.name = prefix + node.name.split(":").pop();
+ return self.widgetFactory.createController(jQuery(node), scope);
+ }});
+ }
+ if (nodeName == 'OPTION') {
+ var html = jQuery('<select/>').append(jQuery(node).clone()).html();
+ if (!html.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
+ if (Binder.hasBinding(node.text)) {
+ jQuery(node).attr('ng-bind-attr', angular.toJson({'value':node.text}));
+ } else {
+ node.value = node.text;
+ }
+ }
+ }
+
+ var children = node.childNodes;
+ for (var k = 0; k < children.length; k++) {
+ this.precompileNode(children[k], path.concat(k), factories);
+ }
+ },
+
+ ng_eval: function(node) {
+ return new EvalUpdater(node, node.getAttribute('ng-eval'));
+ },
+
+ ng_bind: function(node) {
+ return new BindUpdater(node, "{{" + node.getAttribute('ng-bind') + "}}");
+ },
+
+ ng_bind_attr: function(node) {
+ return new BindAttrUpdater(node, fromJson(node.getAttribute('ng-bind-attr')));
+ },
+
+ ng_hide: function(node) {
+ return new HideUpdater(node, node.getAttribute('ng-hide'));
+ },
+
+ ng_show: function(node) {
+ return new ShowUpdater(node, node.getAttribute('ng-show'));
+ },
+
+ ng_class: function(node) {
+ return new ClassUpdater(node, node.getAttribute('ng-class'));
+ },
+
+ ng_class_even: function(node) {
+ return new ClassEvenUpdater(node, node.getAttribute('ng-class-even'));
+ },
+
+ ng_class_odd: function(node) {
+ return new ClassOddUpdater(node, node.getAttribute('ng-class-odd'));
+ },
+
+ ng_style: function(node) {
+ return new StyleUpdater(node, node.getAttribute('ng-style'));
+ },
+
+ ng_watch: function(node, scope) {
+ scope.watch(node.getAttribute('ng-watch'));
+ }
+};
diff --git a/src/delete/Model.js b/src/delete/Model.js
new file mode 100644
index 00000000..b09efd0e
--- /dev/null
+++ b/src/delete/Model.js
@@ -0,0 +1,65 @@
+// Single $ is special and does not get searched
+// Double $$ is special an is client only (does not get sent to server)
+
+function Model(entity, initial) {
+ this['$$entity'] = entity;
+ this['$loadFrom'](initial||{});
+ this['$entity'] = entity['title'];
+ this['$migrate']();
+};
+
+Model.copyDirectFields = function(src, dst) {
+ if (src === dst || !src || !dst) return;
+ var isDataField = function(src, dst, field) {
+ return (field.substring(0,2) !== '$$') &&
+ (typeof src[field] !== 'function') &&
+ (typeof dst[field] !== 'function');
+ };
+ for (var field in dst) {
+ if (isDataField(src, dst, field))
+ delete dst[field];
+ }
+ for (field in src) {
+ if (isDataField(src, dst, field))
+ dst[field] = src[field];
+ }
+};
+
+extend(Model.prototype, {
+ '$migrate': function() {
+ merge(this['$$entity']['defaults'], this);
+ return this;
+ },
+
+ '$merge': function(other) {
+ merge(other, this);
+ return this;
+ },
+
+ '$save': function(callback) {
+ this['$$entity'].datastore.save(this, callback === true ? undefined : callback);
+ if (callback === true) this['$$entity'].datastore.flush();
+ return this;
+ },
+
+ '$delete': function(callback) {
+ this['$$entity'].datastore.remove(this, callback === true ? undefined : callback);
+ if (callback === true) this['$$entity'].datastore.flush();
+ return this;
+ },
+
+ '$loadById': function(id, callback) {
+ this['$$entity'].datastore.load(this, id, callback);
+ return this;
+ },
+
+ '$loadFrom': function(other) {
+ Model.copyDirectFields(other, this);
+ return this;
+ },
+
+ '$saveTo': function(other) {
+ Model.copyDirectFields(this, other);
+ return this;
+ }
+}); \ No newline at end of file
diff --git a/src/delete/Scope.js b/src/delete/Scope.js
new file mode 100644
index 00000000..ae3f9f11
--- /dev/null
+++ b/src/delete/Scope.js
@@ -0,0 +1,407 @@
+function Scope(initialState, name) {
+ var self = this;
+ self.widgets = [];
+ self.evals = [];
+ self.watchListeners = {};
+ self.name = name;
+ initialState = initialState || {};
+ var State = function(){};
+ State.prototype = initialState;
+ self.state = new State();
+ extend(self.state, {
+ '$parent': initialState,
+ '$watch': bind(self, self.addWatchListener),
+ '$eval': bind(self, self.eval),
+ '$bind': bind(self, bind, self),
+ // change name to autoEval?
+ '$addEval': bind(self, self.addEval),
+ '$updateView': bind(self, self.updateView)
+ });
+ if (name == "ROOT") {
+ self.state['$root'] = self.state;
+ }
+};
+
+Scope.expressionCache = {};
+Scope.getter = function(instance, path) {
+ if (!path) return instance;
+ var element = path.split('.');
+ var key;
+ var lastInstance = instance;
+ var len = element.length;
+ for ( var i = 0; i < len; i++) {
+ key = element[i];
+ if (!key.match(/^[\$\w][\$\w\d]*$/))
+ throw "Expression '" + path + "' is not a valid expression for accesing variables.";
+ if (instance) {
+ lastInstance = instance;
+ instance = instance[key];
+ }
+ if (_.isUndefined(instance) && key.charAt(0) == '$') {
+ var type = angular['Global']['typeOf'](lastInstance);
+ type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
+ var fn = type ? type[[key.substring(1)]] : undefined;
+ if (fn) {
+ instance = _.bind(fn, lastInstance, lastInstance);
+ return instance;
+ }
+ }
+ }
+ if (typeof instance === 'function' && !instance['$$factory']) {
+ return bind(lastInstance, instance);
+ }
+ return instance;
+};
+
+Scope.setter = function(instance, path, value){
+ var element = path.split('.');
+ for ( var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var newInstance = instance[key];
+ if (!newInstance) {
+ newInstance = {};
+ instance[key] = newInstance;
+ }
+ instance = newInstance;
+ }
+ instance[element.shift()] = value;
+ return value;
+};
+
+Scope.prototype = {
+ // TODO: rename to update? or eval?
+ updateView: function() {
+ var self = this;
+ this.fireWatchers();
+ foreach(this.widgets, function(widget){
+ self.evalWidget(widget, "", {}, function(){
+ this.updateView(self);
+ });
+ });
+ foreach(this.evals, bind(this, this.apply));
+ },
+
+ addWidget: function(controller) {
+ if (controller) this.widgets.push(controller);
+ },
+
+ addEval: function(fn, listener) {
+ // todo: this should take a function/string and a listener
+ // todo: this is a hack, which will need to be cleaned up.
+ var self = this,
+ listenFn = listener || noop,
+ expr = self.compile(fn);
+ this.evals.push(function(){
+ self.apply(listenFn, expr());
+ });
+ },
+
+ isProperty: function(exp) {
+ for ( var i = 0; i < exp.length; i++) {
+ var ch = exp.charAt(i);
+ if (ch!='.' && !Lexer.prototype.isIdent(ch)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ get: function(path) {
+// log('SCOPE.get', path, Scope.getter(this.state, path));
+ return Scope.getter(this.state, path);
+ },
+
+ set: function(path, value) {
+// log('SCOPE.set', path, value);
+ var instance = this.state;
+ return Scope.setter(instance, path, value);
+ },
+
+ setEval: function(expressionText, value) {
+ this.eval(expressionText + "=" + toJson(value));
+ },
+
+ compile: function(exp) {
+ if (isFunction(exp)) return bind(this.state, exp);
+ var expFn = Scope.expressionCache[exp], self = this;
+ if (!expFn) {
+ var parser = new Parser(exp);
+ expFn = parser.statements();
+ parser.assertAllConsumed();
+ Scope.expressionCache[exp] = expFn;
+ }
+ return function(context){
+ context = context || {};
+ context.self = self.state;
+ context.scope = self;
+ return expFn.call(self, context);
+ };
+ },
+
+ eval: function(exp, context) {
+// log('Scope.eval', expressionText);
+ return this.compile(exp)(context);
+ },
+
+ //TODO: Refactor. This function needs to be an execution closure for widgets
+ // move to widgets
+ // remove expression, just have inner closure.
+ evalWidget: function(widget, expression, context, onSuccess, onFailure) {
+ try {
+ var value = this.eval(expression, context);
+ if (widget.hasError) {
+ widget.hasError = false;
+ jQuery(widget.view).
+ removeClass('ng-exception').
+ removeAttr('ng-error');
+ }
+ if (onSuccess) {
+ value = onSuccess.apply(widget, [value]);
+ }
+ return true;
+ } catch (e){
+ var jsonError = toJson(e, true);
+ error('Eval Widget Error:', jsonError);
+ widget.hasError = true;
+ jQuery(widget.view).
+ addClass('ng-exception').
+ attr('ng-error', jsonError);
+ if (onFailure) {
+ onFailure.apply(widget, [e, jsonError]);
+ }
+ return false;
+ }
+ },
+
+ validate: function(expressionText, value, element) {
+ var expression = Scope.expressionCache[expressionText];
+ if (!expression) {
+ expression = new Parser(expressionText).validator();
+ Scope.expressionCache[expressionText] = expression;
+ }
+ var self = {scope:this, self:this.state, '$element':element};
+ return expression(self)(self, value);
+ },
+
+ entity: function(entityDeclaration, datastore) {
+ var expression = new Parser(entityDeclaration).entityDeclaration();
+ return expression({scope:this, datastore:datastore});
+ },
+
+ clearInvalid: function() {
+ var invalid = this.state['$invalidWidgets'];
+ while(invalid.length > 0) {invalid.pop();}
+ },
+
+ markInvalid: function(widget) {
+ this.state['$invalidWidgets'].push(widget);
+ },
+
+ watch: function(declaration) {
+ var self = this;
+ new Parser(declaration).watch()({
+ scope:this,
+ addListener:function(watch, exp){
+ self.addWatchListener(watch, function(n,o){
+ try {
+ return exp({scope:self}, n, o);
+ } catch(e) {
+ alert(e);
+ }
+ });
+ }
+ });
+ },
+
+ addWatchListener: function(watchExpression, listener) {
+ // TODO: clean me up!
+ if (!isFunction(listener)) {
+ listener = this.compile(listener);
+ }
+ var watcher = this.watchListeners[watchExpression];
+ if (!watcher) {
+ watcher = {listeners:[], expression:watchExpression};
+ this.watchListeners[watchExpression] = watcher;
+ }
+ watcher.listeners.push(listener);
+ },
+
+ fireWatchers: function() {
+ var self = this, fired = false;
+ foreach(this.watchListeners, function(watcher) {
+ var value = self.eval(watcher.expression);
+ if (value !== watcher.lastValue) {
+ foreach(watcher.listeners, function(listener){
+ listener(value, watcher.lastValue);
+ fired = true;
+ });
+ watcher.lastValue = value;
+ }
+ });
+ return fired;
+ },
+
+ apply: function(fn) {
+ fn.apply(this.state, slice.call(arguments, 1, arguments.length));
+ }
+};
+
+//////////////////////////////
+
+function getter(instance, path) {
+ if (!path) return instance;
+ var element = path.split('.');
+ var key;
+ var lastInstance = instance;
+ var len = element.length;
+ for ( var i = 0; i < len; i++) {
+ key = element[i];
+ if (!key.match(/^[\$\w][\$\w\d]*$/))
+ throw "Expression '" + path + "' is not a valid expression for accesing variables.";
+ if (instance) {
+ lastInstance = instance;
+ instance = instance[key];
+ }
+ if (_.isUndefined(instance) && key.charAt(0) == '$') {
+ var type = angular['Global']['typeOf'](lastInstance);
+ type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
+ var fn = type ? type[[key.substring(1)]] : undefined;
+ if (fn) {
+ instance = _.bind(fn, lastInstance, lastInstance);
+ return instance;
+ }
+ }
+ }
+ if (typeof instance === 'function' && !instance['$$factory']) {
+ return bind(lastInstance, instance);
+ }
+ return instance;
+};
+
+function setter(instance, path, value){
+ var element = path.split('.');
+ for ( var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var newInstance = instance[key];
+ if (!newInstance) {
+ newInstance = {};
+ instance[key] = newInstance;
+ }
+ instance = newInstance;
+ }
+ instance[element.shift()] = value;
+ return value;
+};
+
+var compileCache = {};
+function expressionCompile(exp){
+ if (isFunction(exp)) return exp;
+ var expFn = compileCache[exp];
+ if (!expFn) {
+ var parser = new Parser(exp);
+ expFn = parser.statements();
+ parser.assertAllConsumed();
+ compileCache[exp] = expFn;
+ }
+ // return expFn
+ // TODO(remove this hack)
+ return function(){
+ return expFn({
+ scope: {
+ set: this.$set,
+ get: this.$get
+ }
+ });
+ };
+};
+
+var NON_RENDERABLE_ELEMENTS = {
+ '#text': 1, '#comment':1, 'TR':1, 'TH':1
+};
+
+function isRenderableElement(element){
+ return element && element[0] && !NON_RENDERABLE_ELEMENTS[element[0].nodeName];
+}
+
+function rethrow(e) { throw e; }
+function errorHandlerFor(element) {
+ while (!isRenderableElement(element)) {
+ element = element.parent() || jqLite(document.body);
+ }
+ return function(error) {
+ element.attr('ng-error', angular.toJson(error));
+ element.addClass('ng-exception');
+ };
+}
+
+function createScope(parent, Class) {
+ function Parent(){}
+ function API(){}
+ function Behavior(){}
+
+ var instance, behavior, api, watchList = [], evalList = [];
+
+ Class = Class || noop;
+ parent = Parent.prototype = parent || {};
+ api = API.prototype = new Parent();
+ behavior = Behavior.prototype = extend(new API(), Class.prototype);
+ instance = new Behavior();
+
+ extend(api, {
+ $parent: parent,
+ $bind: bind(instance, bind, instance),
+ $get: bind(instance, getter, instance),
+ $set: bind(instance, setter, instance),
+
+ $eval: function(exp) {
+ if (isDefined(exp)) {
+ return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
+ } else {
+ foreach(evalList, function(eval) {
+ instance.$tryEval(eval.fn, eval.handler);
+ });
+ foreach(watchList, function(watch) {
+ var value = instance.$tryEval(watch.watch, watch.handler);
+ if (watch.last !== value) {
+ instance.$tryEval(watch.listener, watch.handler, value, watch.last);
+ watch.last = value;
+ }
+ });
+ }
+ },
+
+ $tryEval: function (expression, exceptionHandler) {
+ try {
+ return expressionCompile(expression).apply(instance, slice.call(arguments, 2, arguments.length));
+ } catch (e) {
+ error(e);
+ if (isFunction(exceptionHandler)) {
+ exceptionHandler(e);
+ } else if (exceptionHandler) {
+ errorHandlerFor(exceptionHandler)(e);
+ }
+ }
+ },
+
+ $watch: function(watchExp, listener, exceptionHandler) {
+ var watch = expressionCompile(watchExp);
+ watchList.push({
+ watch: watch,
+ last: watch.call(instance),
+ handler: exceptionHandler,
+ listener:expressionCompile(listener)
+ });
+ },
+
+ $onEval: function(expr, exceptionHandler){
+ evalList.push({
+ fn: expressionCompile(expr),
+ handler: exceptionHandler
+ });
+ }
+ });
+
+ Class.apply(instance, slice.call(arguments, 2, arguments.length));
+
+ return instance;
+}
diff --git a/src/delete/Widgets.js b/src/delete/Widgets.js
new file mode 100644
index 00000000..96b63793
--- /dev/null
+++ b/src/delete/Widgets.js
@@ -0,0 +1,806 @@
+function WidgetFactory(serverUrl, database) {
+ this.nextUploadId = 0;
+ this.serverUrl = serverUrl;
+ this.database = database;
+ if (window['swfobject']) {
+ this.createSWF = window['swfobject']['createSWF'];
+ } else {
+ this.createSWF = function(){
+ alert("ERROR: swfobject not loaded!");
+ };
+ }
+};
+
+WidgetFactory.prototype = {
+ createController: function(input, scope) {
+ var controller;
+ var type = input.attr('type').toLowerCase();
+ var exp = input.attr('name');
+ if (exp) exp = exp.split(':').pop();
+ var event = "change";
+ var bubbleEvent = true;
+ var formatter = angularFormatter[input.attr('ng-format')] || angularFormatter['noop'];
+ if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') {
+ controller = new ButtonController(input[0], exp, formatter);
+ event = "click";
+ bubbleEvent = false;
+ } else if (type == 'text' || type == 'textarea' || type == 'hidden' || type == 'password') {
+ controller = new TextController(input[0], exp, formatter);
+ event = "keyup change";
+ } else if (type == 'checkbox') {
+ controller = new CheckboxController(input[0], exp, formatter);
+ event = "click";
+ } else if (type == 'radio') {
+ controller = new RadioController(input[0], exp, formatter);
+ event="click";
+ } else if (type == 'select-one') {
+ controller = new SelectController(input[0], exp, formatter);
+ } else if (type == 'select-multiple') {
+ controller = new MultiSelectController(input[0], exp, formatter);
+ } else if (type == 'file') {
+ controller = this.createFileController(input, exp, formatter);
+ } else {
+ throw 'Unknown type: ' + type;
+ }
+ input.data('controller', controller);
+ var updateView = scope.get('$updateView');
+ var action = function() {
+ if (controller.updateModel(scope)) {
+ var action = jQuery(controller.view).attr('ng-action') || "";
+ if (scope.evalWidget(controller, action)) {
+ updateView(scope);
+ }
+ }
+ return bubbleEvent;
+ };
+ jQuery(controller.view, ":input").
+ bind(event, action);
+ return controller;
+ },
+
+ createFileController: function(fileInput) {
+ var uploadId = '__uploadWidget_' + (this.nextUploadId++);
+ var view = FileController.template(uploadId);
+ fileInput.after(view);
+ var att = {
+ 'data':this.serverUrl + "/admin/ServerAPI.swf",
+ 'width':"95", 'height':"20", 'align':"top",
+ 'wmode':"transparent"};
+ var par = {
+ 'flashvars':"uploadWidgetId=" + uploadId,
+ 'allowScriptAccess':"always"};
+ var swfNode = this.createSWF(att, par, uploadId);
+ fileInput.remove();
+ var cntl = new FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database);
+ jQuery(swfNode).parent().data('controller', cntl);
+ return cntl;
+ }
+};
+/////////////////////
+// FileController
+///////////////////////
+
+function FileController(view, scopeName, uploader, databaseUrl) {
+ this.view = view;
+ this.uploader = uploader;
+ this.scopeName = scopeName;
+ this.attachmentsPath = databaseUrl + '/_attachments';
+ this.value = null;
+ this.lastValue = undefined;
+};
+
+angularCallbacks['flashEvent'] = function(id, event, args) {
+ var object = document.getElementById(id);
+ var jobject = jQuery(object);
+ var controller = jobject.parent().data("controller");
+ FileController.prototype[event].apply(controller, args);
+ _.defer(jobject.scope().get('$updateView'));
+};
+
+FileController.template = function(id) {
+ return jQuery('<span class="ng-upload-widget">' +
+ '<input type="checkbox" ng-non-bindable="true"/>' +
+ '<object id="' + id + '" />' +
+ '<a></a>' +
+ '<span/>' +
+ '</span>');
+};
+
+extend(FileController.prototype, {
+ 'cancel': noop,
+ 'complete': noop,
+ 'httpStatus': function(status) {
+ alert("httpStatus:" + this.scopeName + " status:" + status);
+ },
+ 'ioError': function() {
+ alert("ioError:" + this.scopeName);
+ },
+ 'open': function() {
+ alert("open:" + this.scopeName);
+ },
+ 'progress':noop,
+ 'securityError': function() {
+ alert("securityError:" + this.scopeName);
+ },
+ 'uploadCompleteData': function(data) {
+ var value = fromJson(data);
+ value.url = this.attachmentsPath + '/' + value.id + '/' + value.text;
+ this.view.find("input").attr('checked', true);
+ var scope = this.view.scope();
+ this.value = value;
+ this.updateModel(scope);
+ this.value = null;
+ },
+ 'select': function(name, size, type) {
+ this.name = name;
+ this.view.find("a").text(name).attr('href', name);
+ this.view.find("span").text(angular['filter']['bytes'](size));
+ this.upload();
+ },
+
+ updateModel: function(scope) {
+ var isChecked = this.view.find("input").attr('checked');
+ var value = isChecked ? this.value : null;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.set(this.scopeName, value);
+ return true;
+ }
+ },
+
+ updateView: function(scope) {
+ var modelValue = scope.get(this.scopeName);
+ if (modelValue && this.value !== modelValue) {
+ this.value = modelValue;
+ this.view.find("a").
+ attr("href", this.value.url).
+ text(this.value.text);
+ this.view.find("span").text(angular['filter']['bytes'](this.value.size));
+ }
+ this.view.find("input").attr('checked', !!modelValue);
+ },
+
+ upload: function() {
+ if (this.name) {
+ this.uploader['uploadFile'](this.attachmentsPath);
+ }
+ }
+});
+
+///////////////////////
+// NullController
+///////////////////////
+function NullController(view) {this.view = view;};
+NullController.prototype = {
+ updateModel: function() { return true; },
+ updateView: noop
+};
+NullController.instance = new NullController();
+
+
+///////////////////////
+// ButtonController
+///////////////////////
+var ButtonController = NullController;
+
+///////////////////////
+// TextController
+///////////////////////
+function TextController(view, exp, formatter) {
+ this.view = view;
+ this.formatter = formatter;
+ this.exp = exp;
+ this.validator = view.getAttribute('ng-validate');
+ this.required = typeof view.attributes['ng-required'] != "undefined";
+ this.lastErrorText = null;
+ this.lastValue = undefined;
+ this.initialValue = this.formatter['parse'](view.value);
+ var widget = view.getAttribute('ng-widget');
+ if (widget === 'datepicker') {
+ jQuery(view).datepicker();
+ }
+};
+
+TextController.prototype = {
+ updateModel: function(scope) {
+ var value = this.formatter['parse'](this.view.value);
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+ },
+
+ updateView: function(scope) {
+ var view = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ value = value ? value : '';
+ if (!_(this.lastValue).isEqual(value)) {
+ view.value = this.formatter['format'](value);
+ this.lastValue = value;
+ }
+
+ var isValidationError = false;
+ view.removeAttribute('ng-error');
+ if (this.required) {
+ isValidationError = !(value && $.trim("" + value).length > 0);
+ }
+ var errorText = isValidationError ? "Required Value" : null;
+ if (!isValidationError && this.validator && value) {
+ errorText = scope.validate(this.validator, value, view);
+ isValidationError = !!errorText;
+ }
+ if (this.lastErrorText !== errorText) {
+ this.lastErrorText = isValidationError;
+ if (errorText && isVisible(view)) {
+ view.setAttribute('ng-error', errorText);
+ scope.markInvalid(this);
+ }
+ jQuery(view).toggleClass('ng-validation-error', isValidationError);
+ }
+ }
+};
+
+///////////////////////
+// CheckboxController
+///////////////////////
+function CheckboxController(view, exp, formatter) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.formatter = formatter;
+ this.initialValue = this.formatter['parse'](view.checked ? view.value : "");
+};
+
+CheckboxController.prototype = {
+ updateModel: function(scope) {
+ var input = this.view;
+ var value = input.checked ? input.value : '';
+ value = this.formatter['parse'](value);
+ value = this.formatter['format'](value);
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, this.formatter['parse'](value));
+ this.lastValue = value;
+ return true;
+ }
+ },
+
+ updateView: function(scope) {
+ var input = this.view;
+ var value = scope.eval(this.exp);
+ if (typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ input.checked = this.formatter['parse'](input.value) == value;
+ }
+};
+
+///////////////////////
+// SelectController
+///////////////////////
+function SelectController(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.value;
+};
+
+SelectController.prototype = {
+ updateModel: function(scope) {
+ var input = this.view;
+ if (input.selectedIndex < 0) {
+ scope.setEval(this.exp, null);
+ } else {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+ }
+ },
+
+ updateView: function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === 'undefined') {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ if (value !== this.lastValue) {
+ input.value = value ? value : "";
+ this.lastValue = value;
+ }
+ }
+};
+
+///////////////////////
+// MultiSelectController
+///////////////////////
+function MultiSelectController(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = this.selected();
+};
+
+MultiSelectController.prototype = {
+ selected: function () {
+ var value = [];
+ var options = this.view.options;
+ for ( var i = 0; i < options.length; i++) {
+ var option = options[i];
+ if (option.selected) {
+ value.push(option.value);
+ }
+ }
+ return value;
+ },
+
+ updateModel: function(scope) {
+ var value = this.selected();
+ // TODO: This is wrong! no caching going on here as we are always comparing arrays
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+ },
+
+ updateView: function(scope) {
+ var input = this.view;
+ var selected = scope.get(this.exp);
+ if (typeof selected === "undefined") {
+ selected = this.initialValue;
+ scope.setEval(this.exp, selected);
+ }
+ if (selected !== this.lastValue) {
+ var options = input.options;
+ for ( var i = 0; i < options.length; i++) {
+ var option = options[i];
+ option.selected = _.include(selected, option.value);
+ }
+ this.lastValue = selected;
+ }
+ }
+};
+
+///////////////////////
+// RadioController
+///////////////////////
+function RadioController(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastChecked = undefined;
+ this.lastValue = undefined;
+ this.inputValue = view.value;
+ this.initialValue = view.checked ? view.value : null;
+};
+
+RadioController.prototype = {
+ updateModel: function(scope) {
+ var input = this.view;
+ if (this.lastChecked) {
+ return false;
+ } else {
+ input.checked = true;
+ this.lastValue = scope.setEval(this.exp, this.inputValue);
+ this.lastChecked = true;
+ return true;
+ }
+ },
+
+ updateView: function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (this.initialValue && typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ if (this.lastValue != value) {
+ this.lastChecked = input.checked = this.inputValue == (''+value);
+ this.lastValue = value;
+ }
+ }
+};
+
+///////////////////////
+//ElementController
+///////////////////////
+function BindUpdater(view, exp) {
+ this.view = view;
+ this.exp = Binder.parseBindings(exp);
+ this.hasError = false;
+};
+
+BindUpdater.toText = function(obj) {
+ var e = escapeHtml;
+ switch(typeof obj) {
+ case "string":
+ case "boolean":
+ case "number":
+ return e(obj);
+ case "function":
+ return BindUpdater.toText(obj());
+ case "object":
+ if (isNode(obj)) {
+ return outerHTML(obj);
+ } else if (obj instanceof angular.filter.Meta) {
+ switch(typeof obj.html) {
+ case "string":
+ case "number":
+ return obj.html;
+ case "function":
+ return obj.html();
+ case "object":
+ if (isNode(obj.html))
+ return outerHTML(obj.html);
+ default:
+ break;
+ }
+ switch(typeof obj.text) {
+ case "string":
+ case "number":
+ return e(obj.text);
+ case "function":
+ return e(obj.text());
+ default:
+ break;
+ }
+ }
+ if (obj === null)
+ return "";
+ return e(toJson(obj, true));
+ default:
+ return "";
+ }
+};
+
+BindUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ var html = [];
+ var parts = this.exp;
+ var length = parts.length;
+ for(var i=0; i<length; i++) {
+ var part = parts[i];
+ var binding = Binder.binding(part);
+ if (binding) {
+ scope.evalWidget(this, binding, {$element:this.view}, function(value){
+ html.push(BindUpdater.toText(value));
+ }, function(e, text){
+ setHtml(this.view, text);
+ });
+ if (this.hasError) {
+ return;
+ }
+ } else {
+ html.push(escapeHtml(part));
+ }
+ }
+ setHtml(this.view, html.join(''));
+ }
+};
+
+function BindAttrUpdater(view, attrs) {
+ this.view = view;
+ this.attrs = attrs;
+};
+
+BindAttrUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ var jNode = jQuery(this.view);
+ var attributeTemplates = this.attrs;
+ if (this.hasError) {
+ this.hasError = false;
+ jNode.
+ removeClass('ng-exception').
+ removeAttr('ng-error');
+ }
+ var isImage = jNode.is('img');
+ for (var attrName in attributeTemplates) {
+ var attributeTemplate = Binder.parseBindings(attributeTemplates[attrName]);
+ var attrValues = [];
+ for ( var i = 0; i < attributeTemplate.length; i++) {
+ var binding = Binder.binding(attributeTemplate[i]);
+ if (binding) {
+ try {
+ var value = scope.eval(binding, {$element:jNode[0], attrName:attrName});
+ if (value && (value.constructor !== array || value.length !== 0))
+ attrValues.push(value);
+ } catch (e) {
+ this.hasError = true;
+ error('BindAttrUpdater', e);
+ var jsonError = toJson(e, true);
+ attrValues.push('[' + jsonError + ']');
+ jNode.
+ addClass('ng-exception').
+ attr('ng-error', jsonError);
+ }
+ } else {
+ attrValues.push(attributeTemplate[i]);
+ }
+ }
+ var attrValue = attrValues.length ? attrValues.join('') : null;
+ if(isImage && attrName == 'src' && !attrValue)
+ attrValue = scope.get('$config.blankImage');
+ jNode.attr(attrName, attrValue);
+ }
+ }
+};
+
+function EvalUpdater(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.hasError = false;
+};
+EvalUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp);
+ }
+};
+
+function HideUpdater(view, exp) { this.view = view; this.exp = exp; };
+HideUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (toBoolean(hideValue)) {
+ view.hide();
+ } else {
+ view.show();
+ }
+ });
+ }
+};
+
+function ShowUpdater(view, exp) { this.view = view; this.exp = exp; };
+ShowUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (toBoolean(hideValue)) {
+ view.show();
+ } else {
+ view.hide();
+ }
+ });
+ }
+};
+
+function ClassUpdater(view, exp) { this.view = view; this.exp = exp; };
+ClassUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ if (classValue !== null && classValue !== undefined) {
+ this.view.className = classValue;
+ }
+ });
+ }
+};
+
+function ClassEvenUpdater(view, exp) { this.view = view; this.exp = exp; };
+ClassEvenUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 1);
+ });
+ }
+};
+
+function ClassOddUpdater(view, exp) { this.view = view; this.exp = exp; };
+ClassOddUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 0);
+ });
+ }
+};
+
+function StyleUpdater(view, exp) { this.view = view; this.exp = exp; };
+StyleUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(styleValue){
+ jQuery(this.view).attr('style', "").css(styleValue);
+ });
+ }
+};
+
+///////////////////////
+// RepeaterUpdater
+///////////////////////
+function RepeaterUpdater(view, repeaterExpression, template, prefix) {
+ this.view = view;
+ this.template = template;
+ this.prefix = prefix;
+ this.children = [];
+ var match = repeaterExpression.match(/^\s*(.+)\s+in\s+(.*)\s*$/);
+ if (! match) {
+ throw "Expected ng-repeat in form of 'item in collection' but got '" +
+ repeaterExpression + "'.";
+ }
+ var keyValue = match[1];
+ this.iteratorExp = match[2];
+ match = keyValue.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
+ if (!match) {
+ throw "'item' in 'item in collection' should be identifier or (key, value) but get '" +
+ keyValue + "'.";
+ }
+ this.valueExp = match[3] || match[1];
+ this.keyExp = match[2];
+};
+
+RepeaterUpdater.prototype = {
+ updateModel: noop,
+ updateView: function(scope) {
+ scope.evalWidget(this, this.iteratorExp, {}, function(iterator){
+ var self = this;
+ if (!iterator) {
+ iterator = [];
+ if (scope.isProperty(this.iteratorExp)) {
+ scope.set(this.iteratorExp, iterator);
+ }
+ }
+ var childrenLength = this.children.length;
+ var cursor = this.view;
+ var time = 0;
+ var child = null;
+ var keyExp = this.keyExp;
+ var valueExp = this.valueExp;
+ var iteratorCounter = 0;
+ foreach(iterator, function(value, key){
+ if (iteratorCounter < childrenLength) {
+ // reuse children
+ child = self.children[iteratorCounter];
+ child.scope.set(valueExp, value);
+ } else {
+ // grow children
+ var name = self.prefix +
+ valueExp + " in " + self.iteratorExp + "[" + iteratorCounter + "]";
+ var childScope = new Scope(scope.state, name);
+ childScope.set('$index', iteratorCounter);
+ if (keyExp)
+ childScope.set(keyExp, key);
+ childScope.set(valueExp, value);
+ child = { scope:childScope, element:self.template(childScope, self.prefix, iteratorCounter) };
+ cursor.after(child.element);
+ self.children.push(child);
+ }
+ cursor = child.element;
+ var s = new Date().getTime();
+ child.scope.updateView();
+ time += new Date().getTime() - s;
+ iteratorCounter++;
+ });
+ // shrink children
+ for ( var r = childrenLength; r > iteratorCounter; --r) {
+ this.children.pop().element.remove();
+ }
+ // Special case for option in select
+ if (child && child.element[0].nodeName === "OPTION") {
+ var select = jQuery(child.element[0].parentNode);
+ var cntl = select.data('controller');
+ if (cntl) {
+ cntl.lastValue = undefined;
+ cntl.updateView(scope);
+ }
+ }
+ });
+ }
+};
+
+//////////////////////////////////
+// PopUp
+//////////////////////////////////
+
+function PopUp(doc) {
+ this.doc = doc;
+};
+
+PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup";
+
+PopUp.onOver = function(e) {
+ PopUp.onOut();
+ var jNode = jQuery(this);
+ jNode.bind(PopUp.OUT_EVENT, PopUp.onOut);
+ var position = jNode.position();
+ var de = document.documentElement;
+ var w = self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
+ var hasArea = w - position.left;
+ var width = 300;
+ var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error...";
+ var msg = jNode.attr("ng-error");
+
+ var x;
+ var arrowPos = hasArea>(width+75) ? "left" : "right";
+ var tip = jQuery(
+ "<div id='ng-callout' style='width:"+width+"px'>" +
+ "<div class='ng-arrow-"+arrowPos+"'/>" +
+ "<div class='ng-title'>"+title+"</div>" +
+ "<div class='ng-content'>"+msg+"</div>" +
+ "</div>");
+ jQuery("body").append(tip);
+ if(arrowPos === 'left'){
+ x = position.left + this.offsetWidth + 11;
+ }else{
+ x = position.left - (width + 15);
+ tip.find('.ng-arrow-right').css({left:width+1});
+ }
+
+ tip.css({left: x+"px", top: (position.top - 3)+"px"});
+ return true;
+};
+
+PopUp.onOut = function() {
+ jQuery('#ng-callout').
+ unbind(PopUp.OUT_EVENT, PopUp.onOut).
+ remove();
+ return true;
+};
+
+PopUp.prototype = {
+ bind: function () {
+ var self = this;
+ this.doc.find('.ng-validation-error,.ng-exception').
+ live("mouseover", PopUp.onOver);
+ }
+};
+
+//////////////////////////////////
+// Status
+//////////////////////////////////
+
+function NullStatus(body) {
+};
+
+NullStatus.prototype = {
+ beginRequest:function(){},
+ endRequest:function(){}
+};
+
+function Status(body) {
+ this.requestCount = 0;
+ this.body = body;
+};
+
+Status.DOM ='<div id="ng-spacer"></div><div id="ng-loading">loading....</div>';
+
+Status.prototype = {
+ beginRequest: function () {
+ if (this.requestCount === 0) {
+ (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show();
+ }
+ this.requestCount++;
+ },
+
+ endRequest: function () {
+ this.requestCount--;
+ if (this.requestCount === 0) {
+ this.loader.hide("fold");
+ }
+ }
+};
diff --git a/src/directives.js b/src/directives.js
new file mode 100644
index 00000000..cabf0c23
--- /dev/null
+++ b/src/directives.js
@@ -0,0 +1,261 @@
+angularDirective("ng-init", function(expression){
+ return function(element){
+ this.$tryEval(expression, element);
+ };
+});
+
+angularDirective("ng-controller", function(expression){
+ return function(element){
+ var controller = getter(window, expression, true) || getter(this, expression, true);
+ if (!controller)
+ throw "Can not find '"+expression+"' controller.";
+ if (!isFunction(controller))
+ throw "Reference '"+expression+"' is not a class.";
+ this.$become(controller);
+ (this.init || noop)();
+ };
+});
+
+angularDirective("ng-eval", function(expression){
+ return function(element){
+ this.$onEval(expression, element);
+ };
+});
+
+angularDirective("ng-bind", function(expression){
+ return function(element) {
+ var lastValue = noop, lastError = noop;
+ this.$onEval(function() {
+ var error,
+ value = this.$tryEval(expression, function(e){
+ error = toJson(e);
+ }),
+ isHtml,
+ isDomElement;
+ if (lastValue === value && lastError == error) return;
+ isHtml = value instanceof HTML,
+ isDomElement = isElement(value);
+ if (!isHtml && !isDomElement && isObject(value)) {
+ value = toJson(value);
+ }
+ if (value != lastValue || error != lastError) {
+ lastValue = value;
+ lastError = error;
+ elementError(element, NG_EXCEPTION, error);
+ if (error) value = error;
+ if (isHtml) {
+ element.html(value.html);
+ } else if (isDomElement) {
+ element.html('');
+ element.append(value);
+ } else {
+ element.text(value);
+ }
+ }
+ }, element);
+ };
+});
+
+var bindTemplateCache = {};
+function compileBindTemplate(template){
+ var fn = bindTemplateCache[template];
+ if (!fn) {
+ var bindings = [];
+ foreach(parseBindings(template), function(text){
+ var exp = binding(text);
+ bindings.push(exp ? function(element){
+ var error, value = this.$tryEval(exp, function(e){
+ error = toJson(e);
+ });
+ elementError(element, NG_EXCEPTION, error);
+ return error ? error : value;
+ } : function() {
+ return text;
+ });
+ });
+ bindTemplateCache[template] = fn = function(element){
+ var parts = [], self = this;
+ for ( var i = 0; i < bindings.length; i++) {
+ var value = bindings[i].call(self, element);
+ if (isElement(value))
+ value = '';
+ else if (isObject(value))
+ value = toJson(value, true);
+ parts.push(value);
+ };
+ return parts.join('');
+ };
+ }
+ return fn;
+}
+
+angularDirective("ng-bind-template", function(expression){
+ var templateFn = compileBindTemplate(expression);
+ return function(element) {
+ var lastValue;
+ this.$onEval(function() {
+ var value = templateFn.call(this, element);
+ if (value != lastValue) {
+ element.text(value);
+ lastValue = value;
+ }
+ }, element);
+ };
+});
+
+var REMOVE_ATTRIBUTES = {
+ 'disabled':'disabled',
+ 'readonly':'readOnly',
+ 'checked':'checked'
+};
+angularDirective("ng-bind-attr", function(expression){
+ return function(element){
+ var lastValue = {};
+ this.$onEval(function(){
+ var values = this.$eval(expression);
+ for(var key in values) {
+ var value = compileBindTemplate(values[key]).call(this, element),
+ specialName = REMOVE_ATTRIBUTES[lowercase(key)];
+ if (lastValue[key] !== value) {
+ lastValue[key] = value;
+ if (specialName) {
+ if (element[specialName] = toBoolean(value)) {
+ element.attr(specialName, value);
+ } else {
+ element.removeAttr(key);
+ }
+ (element.data('$validate')||noop)();
+ } else {
+ element.attr(key, value);
+ }
+ }
+ };
+ }, element);
+ };
+});
+
+angularWidget("@ng-non-bindable", noop);
+
+angularWidget("@ng-repeat", function(expression, element){
+ element.removeAttr('ng-repeat');
+ element.replaceWith(this.comment("ng-repeat: " + expression));
+ var template = this.compile(element);
+ return function(reference){
+ var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
+ lhs, rhs, valueIdent, keyIdent;
+ if (! match) {
+ throw "Expected ng-repeat in form of 'item in collection' but got '" +
+ expression + "'.";
+ }
+ lhs = match[1];
+ rhs = match[2];
+ match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
+ if (!match) {
+ throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
+ keyValue + "'.";
+ }
+ valueIdent = match[3] || match[1];
+ keyIdent = match[2];
+
+ if (isUndefined(this.$eval(rhs))) this.$set(rhs, []);
+
+ var children = [], currentScope = this;
+ this.$onEval(function(){
+ var index = 0, childCount = children.length, childScope, lastElement = reference,
+ collection = this.$tryEval(rhs, reference);
+ for ( var key in collection) {
+ if (index < childCount) {
+ // reuse existing child
+ childScope = children[index];
+ childScope[valueIdent] = collection[key];
+ if (keyIdent) childScope[keyIdent] = key;
+ } else {
+ // grow children
+ childScope = template(element.clone(), createScope(currentScope));
+ childScope[valueIdent] = collection[key];
+ if (keyIdent) childScope[keyIdent] = key;
+ lastElement.after(childScope.$element);
+ childScope.$index = index;
+ childScope.$element.attr('ng-repeat-index', index);
+ childScope.$init();
+ children.push(childScope);
+ }
+ childScope.$eval();
+ lastElement = childScope.$element;
+ index ++;
+ };
+ // shrink children
+ while(children.length > index) {
+ children.pop().$element.remove();
+ }
+ }, reference);
+ };
+});
+
+angularDirective("ng-click", function(expression, element){
+ return function(element){
+ var self = this;
+ element.bind('click', function(){
+ self.$tryEval(expression, element);
+ self.$root.$eval();
+ return false;
+ });
+ };
+});
+
+angularDirective("ng-watch", function(expression, element){
+ return function(element){
+ var self = this;
+ new Parser(expression).watch()({
+ addListener:function(watch, exp){
+ self.$watch(watch, function(){
+ return exp(self);
+ }, element);
+ }
+ });
+ };
+});
+
+function ngClass(selector) {
+ return function(expression, element){
+ var existing = element[0].className + ' ';
+ return function(element){
+ this.$onEval(function(){
+ var value = this.$eval(expression);
+ if (selector(this.$index)) {
+ if (isArray(value)) value = value.join(' ');
+ element[0].className = trim(existing + value);
+ }
+ }, element);
+ };
+ };
+}
+
+angularDirective("ng-class", ngClass(function(){return true;}));
+angularDirective("ng-class-odd", ngClass(function(i){return i % 2 === 0;}));
+angularDirective("ng-class-even", ngClass(function(i){return i % 2 === 1;}));
+
+angularDirective("ng-show", function(expression, element){
+ return function(element){
+ this.$onEval(function(){
+ element.css('display', toBoolean(this.$eval(expression)) ? '' : 'none');
+ }, element);
+ };
+});
+
+angularDirective("ng-hide", function(expression, element){
+ return function(element){
+ this.$onEval(function(){
+ element.css('display', toBoolean(this.$eval(expression)) ? 'none' : '');
+ }, element);
+ };
+});
+
+angularDirective("ng-style", function(expression, element){
+ return function(element){
+ this.$onEval(function(){
+ element.css(this.$eval(expression));
+ }, element);
+ };
+});
+
diff --git a/src/filters.js b/src/filters.js
new file mode 100644
index 00000000..a911b935
--- /dev/null
+++ b/src/filters.js
@@ -0,0 +1,298 @@
+var angularFilterGoogleChartApi;
+
+foreach({
+ 'currency': function(amount){
+ this.$element.toggleClass('ng-format-negative', amount < 0);
+ return '$' + angularFilter['number'].apply(this, [amount, 2]);
+ },
+
+ 'number': function(amount, fractionSize){
+ if (isNaN(amount) || !isFinite(amount)) {
+ return '';
+ }
+ fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize;
+ var isNegative = amount < 0;
+ amount = Math.abs(amount);
+ var pow = Math.pow(10, fractionSize);
+ var text = "" + Math.round(amount * pow);
+ var whole = text.substring(0, text.length - fractionSize);
+ whole = whole || '0';
+ var frc = text.substring(text.length - fractionSize);
+ text = isNegative ? '-' : '';
+ for (var i = 0; i < whole.length; i++) {
+ if ((whole.length - i)%3 === 0 && i !== 0) {
+ text += ',';
+ }
+ text += whole.charAt(i);
+ }
+ if (fractionSize > 0) {
+ for (var j = frc.length; j < fractionSize; j++) {
+ frc += '0';
+ }
+ text += '.' + frc.substring(0, fractionSize);
+ }
+ return text;
+ },
+
+ 'date': function(amount) {
+ },
+
+ 'json': function(object) {
+ this.$element.addClass("ng-monospace");
+ return toJson(object, true);
+ },
+
+ 'trackPackage': (function(){
+ var MATCHERS = [
+ { name: "UPS",
+ url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=",
+ regexp: [
+ /^1Z[0-9A-Z]{16}$/i]},
+ { name: "FedEx",
+ url: "http://www.fedex.com/Tracking?tracknumbers=",
+ regexp: [
+ /^96\d{10}?$/i,
+ /^96\d{17}?$/i,
+ /^96\d{20}?$/i,
+ /^\d{15}$/i,
+ /^\d{12}$/i]},
+ { name: "USPS",
+ url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=",
+ regexp: [
+ /^(91\d{20})$/i,
+ /^(91\d{18})$/i]}];
+ return function(trackingNo, noMatch) {
+ trackingNo = trim(trackingNo);
+ var tNo = trackingNo.replace(/ /g, '');
+ var returnValue;
+ foreach(MATCHERS, function(carrier){
+ foreach(carrier.regexp, function(regexp){
+ if (!returnValue && regexp.test(tNo)) {
+ var text = carrier.name + ": " + trackingNo;
+ var url = carrier.url + trackingNo;
+ returnValue = jqLite('<a></a>');
+ returnValue.text(text);
+ returnValue.attr('href', url);
+ }
+ });
+ });
+ if (returnValue)
+ return returnValue;
+ else if (trackingNo)
+ return noMatch || trackingNo + " is not recognized";
+ else
+ return null;
+ };})(),
+
+ 'link': function(obj, title) {
+ if (obj) {
+ var text = title || obj.text || obj;
+ var url = obj.url || obj;
+ if (url) {
+ if (angular.validator.email(url) === null) {
+ url = "mailto:" + url;
+ }
+ var a = jqLite('<a></a>');
+ a.attr('href', url);
+ a.text(text);
+ return a;
+ }
+ }
+ return obj;
+ },
+
+
+ 'bytes': (function(){
+ var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ return function(size) {
+ if(size === null) return "";
+
+ var suffix = 0;
+ while (size > 1000) {
+ size = size / 1024;
+ suffix++;
+ }
+ var txt = "" + size;
+ var dot = txt.indexOf('.');
+ if (dot > -1 && dot + 2 < txt.length) {
+ txt = txt.substring(0, dot + 2);
+ }
+ return txt + " " + SUFFIX[suffix];
+ };
+ })(),
+
+ 'image': function(obj, width, height) {
+ if (obj && obj.url) {
+ var style = "", img = jqLite('<img>');
+ if (width) {
+ img.css('max-width', width + 'px');
+ img.css('max-height', (height || width) + 'px');
+ }
+ img.attr('src', obj.url);
+ return img;
+ }
+ return null;
+ },
+
+ 'lowercase': lowercase,
+
+ 'uppercase': uppercase,
+
+ 'linecount': function (obj) {
+ if (isString(obj)) {
+ if (obj==='') return 1;
+ return obj.split(/\n|\f/).length;
+ }
+ return 1;
+ },
+
+ 'if': function (result, expression) {
+ return expression ? result : undefined;
+ },
+
+ 'unless': function (result, expression) {
+ return expression ? undefined : result;
+ },
+
+ 'googleChartApi': extend(
+ function(type, data, width, height) {
+ data = data || {};
+ var chart = {
+ 'cht':type,
+ 'chco':angularFilterGoogleChartApi['collect'](data, 'color'),
+ 'chtt':angularFilterGoogleChartApi['title'](data),
+ 'chdl':angularFilterGoogleChartApi['collect'](data, 'label'),
+ 'chd':angularFilterGoogleChartApi['values'](data),
+ 'chf':'bg,s,FFFFFF00'
+ };
+ if (_.isArray(data['xLabels'])) {
+ chart['chxt']='x';
+ chart['chxl']='0:|' + data.xLabels.join('|');
+ }
+ return angularFilterGoogleChartApi['encode'](chart, width, height);
+ },
+ {
+ 'values': function(data){
+ var seriesValues = [];
+ foreach(data['series']||[], function(serie){
+ var values = [];
+ foreach(serie['values']||[], function(value){
+ values.push(value);
+ });
+ seriesValues.push(values.join(','));
+ });
+ var values = seriesValues.join('|');
+ return values === "" ? null : "t:" + values;
+ },
+
+ 'title': function(data){
+ var titles = [];
+ var title = data['title'] || [];
+ foreach(_.isArray(title)?title:[title], function(text){
+ titles.push(encodeURIComponent(text));
+ });
+ return titles.join('|');
+ },
+
+ 'collect': function(data, key){
+ var outterValues = [];
+ var count = 0;
+ foreach(data['series']||[], function(serie){
+ var innerValues = [];
+ var value = serie[key] || [];
+ foreach(_.isArray(value)?value:[value], function(color){
+ innerValues.push(encodeURIComponent(color));
+ count++;
+ });
+ outterValues.push(innerValues.join('|'));
+ });
+ return count?outterValues.join(','):null;
+ },
+
+ 'encode': function(params, width, height) {
+ width = width || 200;
+ height = height || width;
+ var url = "http://chart.apis.google.com/chart?",
+ urlParam = [],
+ img = jqLite('<img>');
+ params['chs'] = width + "x" + height;
+ foreach(params, function(value, key){
+ if (value) {
+ urlParam.push(key + "=" + value);
+ }
+ });
+ urlParam.sort();
+ url += urlParam.join("&");
+ img.attr('src', url);
+ img.css({width: width + 'px', height: height + 'px'});
+ return img;
+ }
+ }
+ ),
+
+
+ 'qrcode': function(value, width, height) {
+ return angularFilterGoogleChartApi['encode']({
+ 'cht':'qr', 'chl':encodeURIComponent(value)}, width, height);
+ },
+ 'chart': {
+ 'pie':function(data, width, height) {
+ return angularFilterGoogleChartApi('p', data, width, height);
+ },
+ 'pie3d':function(data, width, height) {
+ return angularFilterGoogleChartApi('p3', data, width, height);
+ },
+ 'pieConcentric':function(data, width, height) {
+ return angularFilterGoogleChartApi('pc', data, width, height);
+ },
+ 'barHorizontalStacked':function(data, width, height) {
+ return angularFilterGoogleChartApi('bhs', data, width, height);
+ },
+ 'barHorizontalGrouped':function(data, width, height) {
+ return angularFilterGoogleChartApi('bhg', data, width, height);
+ },
+ 'barVerticalStacked':function(data, width, height) {
+ return angularFilterGoogleChartApi('bvs', data, width, height);
+ },
+ 'barVerticalGrouped':function(data, width, height) {
+ return angularFilterGoogleChartApi('bvg', data, width, height);
+ },
+ 'line':function(data, width, height) {
+ return angularFilterGoogleChartApi('lc', data, width, height);
+ },
+ 'sparkline':function(data, width, height) {
+ return angularFilterGoogleChartApi('ls', data, width, height);
+ },
+ 'scatter':function(data, width, height) {
+ return angularFilterGoogleChartApi('s', data, width, height);
+ }
+ },
+
+ 'html': function(html){
+ return new HTML(html);
+ },
+
+ 'linky': function(text){
+ if (!text) return text;
+ function regExpEscape(text) {
+ return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1');
+ }
+ var URL = /(ftp|http|https|mailto):\/\/([^\(\)|\s]+)/;
+ var match;
+ var raw = text;
+ var html = [];
+ while (match=raw.match(URL)) {
+ var url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
+ var i = raw.indexOf(url);
+ html.push(escapeHtml(raw.substr(0, i)));
+ html.push('<a href="' + url + '">');
+ html.push(url);
+ html.push('</a>');
+ raw = raw.substring(i + url.length);
+ }
+ html.push(escapeHtml(raw));
+ return new HTML(html.join(''));
+ }
+}, function(v,k){angularFilter[k] = v;});
+
+angularFilterGoogleChartApi = angularFilter['googleChartApi'];
diff --git a/src/formatters.js b/src/formatters.js
new file mode 100644
index 00000000..40462cf3
--- /dev/null
+++ b/src/formatters.js
@@ -0,0 +1,32 @@
+function formatter(format, parse) {return {'format':format, 'parse':parse || format};}
+function toString(obj) {return (isDefined(obj) && obj !== null) ? "" + obj : obj;}
+
+var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;
+
+extend(angularFormatter, {
+ 'noop':formatter(identity, identity),
+ 'boolean':formatter(toString, toBoolean),
+ 'number':formatter(toString,
+ function(obj){
+ if (isString(obj) && NUMBER.exec(obj)) {
+ return obj ? 1*obj : null;
+ }
+ throw "Not a number";
+ }),
+
+ 'list':formatter(
+ function(obj) { return obj ? obj.join(", ") : obj; },
+ function(value) {
+ var list = [];
+ foreach((value || '').split(','), function(item){
+ item = trim(item);
+ if (item) list.push(item);
+ });
+ return list;
+ }
+ ),
+
+ 'trim':formatter(
+ function(obj) { return obj ? trim("" + obj) : ""; }
+ )
+});
diff --git a/src/jqLite.js b/src/jqLite.js
new file mode 100644
index 00000000..68172fd8
--- /dev/null
+++ b/src/jqLite.js
@@ -0,0 +1,248 @@
+//////////////////////////////////
+//JQLite
+//////////////////////////////////
+
+var jqCache = {};
+var jqName = 'ng-' + new Date().getTime();
+var jqId = 1;
+function jqNextId() { return (jqId++); }
+
+var addEventListener = window.document.attachEvent ?
+ function(element, type, fn) {
+ element.attachEvent('on' + type, fn);
+ } : function(element, type, fn) {
+ element.addEventListener(type, fn, false);
+ };
+
+var removeEventListener = window.document.detachEvent ?
+ function(element, type, fn) {
+ element.detachEvent('on' + type, fn);
+ } : function(element, type, fn) {
+ element.removeEventListener(type, fn, false);
+ };
+
+function jqClearData(element) {
+ var cacheId = element[jqName],
+ cache = jqCache[cacheId];
+ if (cache) {
+ foreach(cache.bind || {}, function(fn, type){
+ removeEventListener(element, type, fn);
+ });
+ delete jqCache[cacheId];
+ if (msie)
+ element[jqName] = ''; // ie does not allow deletion of attributes on elements.
+ else
+ delete element[jqName];
+ }
+}
+
+function JQLite(element) {
+ if (isElement(element)) {
+ this[0] = element;
+ this.length = 1;
+ } else if (isDefined(element.length) && element.item) {
+ for(var i=0; i < element.length; i++) {
+ this[i] = element[i];
+ }
+ this.length = element.length;
+ }
+}
+
+JQLite.prototype = {
+ data: function(key, value) {
+ var element = this[0],
+ cacheId = element[jqName],
+ cache = jqCache[cacheId || -1];
+ if (isDefined(value)) {
+ if (!cache) {
+ element[jqName] = cacheId = jqNextId();
+ cache = jqCache[cacheId] = {};
+ }
+ cache[key] = value;
+ } else {
+ return cache ? cache[key] : null;
+ }
+ },
+
+ removeData: function(){
+ jqClearData(this[0]);
+ },
+
+ dealoc: function(){
+ (function dealoc(element){
+ jqClearData(element);
+ for ( var i = 0, children = element.childNodes; i < children.length; i++) {
+ dealoc(children[i]);
+ }
+ })(this[0]);
+ },
+
+ bind: function(type, fn){
+ var self = this,
+ element = self[0],
+ bind = self.data('bind'),
+ eventHandler;
+ if (!bind) this.data('bind', bind = {});
+ foreach(type.split(' '), function(type){
+ eventHandler = bind[type];
+ if (!eventHandler) {
+ bind[type] = eventHandler = function(event) {
+ var bubbleEvent = false;
+ foreach(eventHandler.fns, function(fn){
+ bubbleEvent = bubbleEvent || fn.call(self, event);
+ });
+ if (!bubbleEvent) {
+ if (msie) {
+ event.returnValue = false;
+ event.cancelBubble = true;
+ } else {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ };
+ eventHandler.fns = [];
+ addEventListener(element, type, eventHandler);
+ }
+ eventHandler.fns.push(fn);
+ });
+ },
+
+ trigger: function(type) {
+ var evnt = document.createEvent('MouseEvent');
+ evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ this[0].dispatchEvent(evnt);
+ },
+
+ replaceWith: function(replaceNode) {
+ this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]);
+ },
+
+ children: function() {
+ return new JQLite(this[0].childNodes);
+ },
+
+ append: function(node) {
+ var self = this[0];
+ node = jqLite(node);
+ foreach(node, function(child){
+ self.appendChild(child);
+ });
+ },
+
+ remove: function() {
+ this.dealoc();
+ var parentNode = this[0].parentNode;
+ if (parentNode) parentNode.removeChild(this[0]);
+ },
+
+ removeAttr: function(name) {
+ this[0].removeAttribute(name);
+ },
+
+ after: function(element) {
+ this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling);
+ },
+
+ hasClass: function(selector) {
+ var className = " " + selector + " ";
+ if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ return false;
+ },
+
+ removeClass: function(selector) {
+ this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", ""));
+ },
+
+ toggleClass: function(selector, condition) {
+ var self = this;
+ (condition ? self.addClass : self.removeClass).call(self, selector);
+ },
+
+ addClass: function( selector ) {
+ if (!this.hasClass(selector)) {
+ this[0].className = trim(this[0].className + ' ' + selector);
+ }
+ },
+
+ css: function(name, value) {
+ var style = this[0].style;
+ if (isString(name)) {
+ if (isDefined(value)) {
+ style[name] = value;
+ } else {
+ return style[name];
+ }
+ } else {
+ extend(style, name);
+ }
+ },
+
+ attr: function(name, value){
+ var e = this[0];
+ if (isObject(name)) {
+ foreach(name, function(value, name){
+ e.setAttribute(name, value);
+ });
+ } else if (isDefined(value)) {
+ e.setAttribute(name, value);
+ } else {
+ var attributes = e.attributes,
+ item = attributes ? attributes.getNamedItem(name) : undefined;
+ return item && item.specified ? item.value : undefined;
+ }
+ },
+
+ text: function(value) {
+ if (isDefined(value)) {
+ this[0].textContent = value;
+ }
+ return this[0].textContent;
+ },
+
+ val: function(value) {
+ if (isDefined(value)) {
+ this[0].value = value;
+ }
+ return this[0].value;
+ },
+
+ html: function(value) {
+ if (isDefined(value)) {
+ var i = 0, childNodes = this[0].childNodes;
+ for ( ; i < childNodes.length; i++) {
+ jqLite(childNodes[i]).dealoc();
+ }
+ this[0].innerHTML = value;
+ }
+ return this[0].innerHTML;
+ },
+
+ parent: function() {
+ return jqLite(this[0].parentNode);
+ },
+
+ clone: function() { return jqLite(this[0].cloneNode(true)); }
+};
+
+if (msie) {
+ extend(JQLite.prototype, {
+ text: function(value) {
+ var e = this[0];
+ // NodeType == 3 is text node
+ if (e.nodeType == 3) {
+ if (isDefined(value)) e.nodeValue = value;
+ return e.nodeValue;
+ } else {
+ if (isDefined(value)) e.innerText = value;
+ return e.innerText;
+ }
+ },
+
+ trigger: function(type) {
+ this[0].fireEvent('on' + type);
+ }
+ });
+}
diff --git a/src/markups.js b/src/markups.js
new file mode 100644
index 00000000..74b293b8
--- /dev/null
+++ b/src/markups.js
@@ -0,0 +1,85 @@
+function parseBindings(string) {
+ var results = [];
+ var lastIndex = 0;
+ var index;
+ while((index = string.indexOf('{{', lastIndex)) > -1) {
+ if (lastIndex < index)
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+
+ index = string.indexOf('}}', index);
+ index = index < 0 ? string.length : index + 2;
+
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+ }
+ if (lastIndex != string.length)
+ results.push(string.substr(lastIndex, string.length - lastIndex));
+ return results.length === 0 ? [ string ] : results;
+}
+
+function binding(string) {
+ var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
+ return binding ? binding[1] : null;
+}
+
+function hasBindings(bindings) {
+ return bindings.length > 1 || binding(bindings[0]) !== null;
+}
+
+angularTextMarkup('{{}}', function(text, textNode, parentElement) {
+ var bindings = parseBindings(text),
+ self = this;
+ if (hasBindings(bindings)) {
+ if (isLeafNode(parentElement[0])) {
+ parentElement.attr('ng-bind-template', text);
+ } else {
+ var cursor = textNode, newElement;
+ foreach(parseBindings(text), function(text){
+ var exp = binding(text);
+ if (exp) {
+ newElement = self.element('span');
+ newElement.attr('ng-bind', exp);
+ } else {
+ newElement = self.text(text);
+ }
+ if (msie && text.charAt(0) == ' ') {
+ newElement = jqLite('<span>&nbsp;</span>');
+ var nbsp = newElement.html();
+ newElement.text(text.substr(1));
+ newElement.html(nbsp + newElement.html());
+ }
+ cursor.after(newElement);
+ cursor = newElement;
+ });
+ }
+ textNode.remove();
+ }
+});
+
+// TODO: this should be widget not a markup
+angularTextMarkup('OPTION', function(text, textNode, parentElement){
+ if (nodeName(parentElement) == "OPTION") {
+ var select = document.createElement('select');
+ select.insertBefore(parentElement[0].cloneNode(true), null);
+ if (!select.innerHTML.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
+ parentElement.attr('value', text);
+ }
+ }
+});
+
+var NG_BIND_ATTR = 'ng-bind-attr';
+angularAttrMarkup('{{}}', function(value, name, element){
+ if (name.substr(0, 3) != 'ng-') {
+ if (msie && name == 'src')
+ value = decodeURI(value);
+ var bindings = parseBindings(value),
+ bindAttr;
+ if (hasBindings(bindings)) {
+ element.removeAttr(name);
+ bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}");
+ bindAttr[name] = value;
+ element.attr(NG_BIND_ATTR, toJson(bindAttr));
+ }
+ }
+});
diff --git a/src/moveToAngularCom/ControlBar.js b/src/moveToAngularCom/ControlBar.js
new file mode 100644
index 00000000..685beeb2
--- /dev/null
+++ b/src/moveToAngularCom/ControlBar.js
@@ -0,0 +1,72 @@
+function ControlBar(document, serverUrl, database) {
+ this._document = document;
+ this.serverUrl = serverUrl;
+ this.database = database;
+ this._window = window;
+ this.callbacks = [];
+};
+
+ControlBar.HTML =
+ '<div>' +
+ '<div class="ui-widget-overlay"></div>' +
+ '<div id="ng-login" ng-non-bindable="true">' +
+ '<div class="ng-login-container"></div>' +
+ '</div>' +
+ '</div>';
+
+
+ControlBar.FORBIDEN =
+ '<div ng-non-bindable="true" title="Permission Error:">' +
+ 'Sorry, you do not have permission for this!'+
+ '</div>';
+
+ControlBar.prototype = {
+ bind: function () {
+ },
+
+ login: function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/new.mini?database="+encodeURIComponent(this.database)+"&return_url=" + encodeURIComponent(this.urlWithoutAnchor()));
+ }
+ },
+
+ logout: function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/do_destroy.mini");
+ }
+ },
+
+ urlWithoutAnchor: function (path) {
+ return this._window['location']['href'].split("#")[0];
+ },
+
+ doTemplate: function (path) {
+ var self = this;
+ var id = new Date().getTime();
+ var url = this.urlWithoutAnchor() + "#$iframe_notify=" + id;
+ var iframeHeight = 330;
+ var loginView = jQuery('<div style="overflow:hidden; padding:2px 0 0 0;"><iframe name="'+ url +'" src="'+this.serverUrl + path + '" width="500" height="'+ iframeHeight +'"/></div>');
+ this._document.append(loginView);
+ loginView['dialog']({
+ 'height':iframeHeight + 33, 'width':500,
+ 'resizable': false, 'modal':true,
+ 'title': 'Authentication: <a href="http://www.getangular.com"><tt>&lt;angular/&gt;</tt></a>'
+ });
+ angularCallbacks["_iframe_notify_" + id] = function() {
+ loginView['dialog']("destroy");
+ loginView['remove']();
+ foreach(self.callbacks, function(callback){
+ callback();
+ });
+ self.callbacks = [];
+ };
+ },
+
+ notAuthorized: function () {
+ if (this.forbidenView) return;
+ this.forbidenView = jQuery(ControlBar.FORBIDEN);
+ this.forbidenView.dialog({bgiframe:true, height:70, modal:true});
+ }
+}; \ No newline at end of file
diff --git a/src/moveToAngularCom/DataStore.js b/src/moveToAngularCom/DataStore.js
new file mode 100644
index 00000000..70bcc623
--- /dev/null
+++ b/src/moveToAngularCom/DataStore.js
@@ -0,0 +1,330 @@
+function DataStore(post, users, anchor) {
+ this.post = post;
+ this.users = users;
+ this._cache_collections = [];
+ this._cache = {'$collections':this._cache_collections};
+ this.anchor = anchor;
+ this.bulkRequest = [];
+};
+
+DataStore.NullEntity = extend(function(){}, {
+ 'all': function(){return [];},
+ 'query': function(){return [];},
+ 'load': function(){return {};},
+ 'title': undefined
+});
+
+DataStore.prototype = {
+ cache: function(document) {
+ if (! document.datastore === this) {
+ throw "Parameter must be an instance of Entity! " + toJson(document);
+ }
+ var key = document['$entity'] + '/' + document['$id'];
+ var cachedDocument = this._cache[key];
+ if (cachedDocument) {
+ Model.copyDirectFields(document, cachedDocument);
+ } else {
+ this._cache[key] = document;
+ cachedDocument = document;
+ }
+ return cachedDocument;
+ },
+
+ load: function(instance, id, callback, failure) {
+ if (id && id !== '*') {
+ var self = this;
+ this._jsonRequest(["GET", instance['$entity'] + "/" + id], function(response) {
+ instance['$loadFrom'](response);
+ instance['$migrate']();
+ var clone = instance['$$entity'](instance);
+ self.cache(clone);
+ (callback||noop)(instance);
+ }, failure);
+ }
+ return instance;
+ },
+
+ loadMany: function(entity, ids, callback) {
+ var self=this;
+ var list = [];
+ var callbackCount = 0;
+ foreach(ids, function(id){
+ list.push(self.load(entity(), id, function(){
+ callbackCount++;
+ if (callbackCount == ids.length) {
+ (callback||noop)(list);
+ }
+ }));
+ });
+ return list;
+ },
+
+ loadOrCreate: function(instance, id, callback) {
+ var self=this;
+ return this.load(instance, id, callback, function(response){
+ if (response['$status_code'] == 404) {
+ instance['$id'] = id;
+ (callback||noop)(instance);
+ } else {
+ throw response;
+ }
+ });
+ },
+
+ loadAll: function(entity, callback) {
+ var self = this;
+ var list = [];
+ list['$$accept'] = function(doc){
+ return doc['$entity'] == entity['title'];
+ };
+ this._cache_collections.push(list);
+ this._jsonRequest(["GET", entity['title']], function(response) {
+ var rows = response;
+ for ( var i = 0; i < rows.length; i++) {
+ var document = entity();
+ document['$loadFrom'](rows[i]);
+ list.push(self.cache(document));
+ }
+ (callback||noop)(list);
+ });
+ return list;
+ },
+
+ save: function(document, callback) {
+ var self = this;
+ var data = {};
+ document['$saveTo'](data);
+ this._jsonRequest(["POST", "", data], function(response) {
+ document['$loadFrom'](response);
+ var cachedDoc = self.cache(document);
+ _.each(self._cache_collections, function(collection){
+ if (collection['$$accept'](document)) {
+ angularArray['includeIf'](collection, cachedDoc, true);
+ }
+ });
+ if (document['$$anchor']) {
+ self.anchor[document['$$anchor']] = document['$id'];
+ }
+ if (callback)
+ callback(document);
+ });
+ },
+
+ remove: function(document, callback) {
+ var self = this;
+ var data = {};
+ document['$saveTo'](data);
+ this._jsonRequest(["DELETE", "", data], function(response) {
+ delete self._cache[document['$entity'] + '/' + document['$id']];
+ _.each(self._cache_collections, function(collection){
+ for ( var i = 0; i < collection.length; i++) {
+ var item = collection[i];
+ if (item['$id'] == document['$id']) {
+ collection.splice(i, 1);
+ }
+ }
+ });
+ (callback||noop)(response);
+ });
+ },
+
+ _jsonRequest: function(request, callback, failure) {
+ request['$$callback'] = callback;
+ request['$$failure'] = failure||function(response){
+ throw response;
+ };
+ this.bulkRequest.push(request);
+ },
+
+ flush: function() {
+ if (this.bulkRequest.length === 0) return;
+ var self = this;
+ var bulkRequest = this.bulkRequest;
+ this.bulkRequest = [];
+ log('REQUEST:', bulkRequest);
+ function callback(code, bulkResponse){
+ log('RESPONSE[' + code + ']: ', bulkResponse);
+ if(bulkResponse['$status_code'] == 401) {
+ self.users['login'](function(){
+ self.post(bulkRequest, callback);
+ });
+ } else if(bulkResponse['$status_code']) {
+ alert(toJson(bulkResponse));
+ } else {
+ for ( var i = 0; i < bulkResponse.length; i++) {
+ var response = bulkResponse[i];
+ var request = bulkRequest[i];
+ var responseCode = response['$status_code'];
+ if(responseCode) {
+ if(responseCode == 403) {
+ self.users['notAuthorized']();
+ } else {
+ request['$$failure'](response);
+ }
+ } else {
+ request['$$callback'](response);
+ }
+ }
+ }
+ }
+ this.post(bulkRequest, callback);
+ },
+
+ saveScope: function(scope, callback) {
+ var saveCounter = 1;
+ function onSaveDone() {
+ saveCounter--;
+ if (saveCounter === 0 && callback)
+ callback();
+ }
+ for(var key in scope) {
+ var item = scope[key];
+ if (item && item['$save'] == Model.prototype['$save']) {
+ saveCounter++;
+ item['$save'](onSaveDone);
+ }
+ }
+ onSaveDone();
+ },
+
+ query: function(type, query, arg, callback){
+ var self = this;
+ var queryList = [];
+ queryList['$$accept'] = function(doc){
+ return false;
+ };
+ this._cache_collections.push(queryList);
+ var request = type['title'] + '/' + query + '=' + arg;
+ this._jsonRequest(["GET", request], function(response){
+ var list = response;
+ foreach(list, function(item){
+ var document = type()['$loadFrom'](item);
+ queryList.push(self.cache(document));
+ });
+ (callback||noop)(queryList);
+ });
+ return queryList;
+ },
+
+ entities: function(callback) {
+ var entities = [];
+ var self = this;
+ this._jsonRequest(["GET", "$entities"], function(response) {
+ foreach(response, function(value, entityName){
+ entities.push(self.entity(entityName));
+ });
+ entities.sort(function(a,b){return a.title > b.title ? 1 : -1;});
+ (callback||noop)(entities);
+ });
+ return entities;
+ },
+
+ documentCountsByUser: function(){
+ var counts = {};
+ var self = this;
+ self.post([["GET", "$users"]], function(code, response){
+ extend(counts, response[0]);
+ });
+ return counts;
+ },
+
+ userDocumentIdsByEntity: function(user){
+ var ids = {};
+ var self = this;
+ self.post([["GET", "$users/" + user]], function(code, response){
+ extend(ids, response[0]);
+ });
+ return ids;
+ },
+
+ entity: function(name, defaults){
+ if (!name) {
+ return DataStore.NullEntity;
+ }
+ var self = this;
+ var entity = extend(function(initialState){
+ return new Model(entity, initialState);
+ }, {
+ // entity.name does not work as name seems to be reserved for functions
+ 'title': name,
+ '$$factory': true,
+ datastore: this, //private, obfuscate
+ 'defaults': defaults || {},
+ 'load': function(id, callback){
+ return self.load(entity(), id, callback);
+ },
+ 'loadMany': function(ids, callback){
+ return self.loadMany(entity, ids, callback);
+ },
+ 'loadOrCreate': function(id, callback){
+ return self.loadOrCreate(entity(), id, callback);
+ },
+ 'all': function(callback){
+ return self.loadAll(entity, callback);
+ },
+ 'query': function(query, queryArgs, callback){
+ return self.query(entity, query, queryArgs, callback);
+ },
+ 'properties': function(callback) {
+ self._jsonRequest(["GET", name + "/$properties"], callback);
+ }
+ });
+ return entity;
+ },
+
+ join: function(join){
+ function fn(){
+ throw "Joined entities can not be instantiated into a document.";
+ };
+ function base(name){return name ? name.substring(0, name.indexOf('.')) : undefined;}
+ function next(name){return name.substring(name.indexOf('.') + 1);}
+ var joinOrder = _(join).chain().
+ map(function($, name){
+ return name;}).
+ sortBy(function(name){
+ var path = [];
+ do {
+ if (_(path).include(name)) throw "Infinite loop in join: " + path.join(" -> ");
+ path.push(name);
+ if (!join[name]) throw _("Named entity '<%=name%>' is undefined.").template({name:name});
+ name = base(join[name].on);
+ } while(name);
+ return path.length;
+ }).
+ value();
+ if (_(joinOrder).select(function($){return join[$].on;}).length != joinOrder.length - 1)
+ throw "Exactly one entity needs to be primary.";
+ fn['query'] = function(exp, value) {
+ var joinedResult = [];
+ var baseName = base(exp);
+ if (baseName != joinOrder[0]) throw _("Named entity '<%=name%>' is not a primary entity.").template({name:baseName});
+ var Entity = join[baseName].join;
+ var joinIndex = 1;
+ Entity['query'](next(exp), value, function(result){
+ var nextJoinName = joinOrder[joinIndex++];
+ var nextJoin = join[nextJoinName];
+ var nextJoinOn = nextJoin.on;
+ var joinIds = {};
+ _(result).each(function(doc){
+ var row = {};
+ joinedResult.push(row);
+ row[baseName] = doc;
+ var id = Scope.getter(row, nextJoinOn);
+ joinIds[id] = id;
+ });
+ nextJoin.join.loadMany(_.toArray(joinIds), function(result){
+ var byId = {};
+ _(result).each(function(doc){
+ byId[doc.$id] = doc;
+ });
+ _(joinedResult).each(function(row){
+ var id = Scope.getter(row, nextJoinOn);
+ row[nextJoinName] = byId[id];
+ });
+ });
+ });
+ return joinedResult;
+ };
+ return fn;
+ }
+};
diff --git a/src/moveToAngularCom/Server.js b/src/moveToAngularCom/Server.js
new file mode 100644
index 00000000..5c4ec3c6
--- /dev/null
+++ b/src/moveToAngularCom/Server.js
@@ -0,0 +1,68 @@
+function Server(url, getScript) {
+ this.url = url;
+ this.nextId = 0;
+ this.getScript = getScript;
+ this.uuid = "_" + ("" + Math.random()).substr(2) + "_";
+ this.maxSize = 1800;
+};
+
+Server.prototype = {
+ base64url: function(txt) {
+ return Base64.encode(txt);
+ },
+
+ request: function(method, url, request, callback) {
+ var requestId = this.uuid + (this.nextId++);
+ var payload = this.base64url(toJson({'u':url, 'm':method, 'p':request}));
+ var totalPockets = Math.ceil(payload.length / this.maxSize);
+ var baseUrl = this.url + "/$/" + requestId + "/" + totalPockets + "/";
+ angularCallbacks[requestId] = function(response) {
+ delete angularCallbacks[requestId];
+ callback(200, response);
+ };
+ for ( var pocketNo = 0; pocketNo < totalPockets; pocketNo++) {
+ var pocket = payload.substr(pocketNo * this.maxSize, this.maxSize);
+ this.getScript(baseUrl + (pocketNo+1) + "?h=" + pocket, noop);
+ }
+ }
+};
+
+function FrameServer(frame) {
+ this.frame = frame;
+};
+FrameServer.PREFIX = "$DATASET:";
+
+FrameServer.prototype = {
+ read:function(){
+ this.data = fromJson(this.frame.name.substr(FrameServer.PREFIX.length));
+ },
+ write:function(){
+ this.frame.name = FrameServer.PREFIX + toJson(this.data);
+ },
+ request: function(method, url, request, callback) {
+ //alert(method + " " + url + " " + toJson(request) + " " + toJson(callback));
+ }
+};
+
+
+function VisualServer(delegate, status, update) {
+ this.delegate = delegate;
+ this.update = update;
+ this.status = status;
+};
+
+VisualServer.prototype = {
+ request:function(method, url, request, callback) {
+ var self = this;
+ this.status.beginRequest(request);
+ this.delegate.request(method, url, request, function() {
+ self.status.endRequest();
+ try {
+ callback.apply(this, arguments);
+ } catch (e) {
+ alert(toJson(e));
+ }
+ self.update();
+ });
+ }
+};
diff --git a/src/moveToAngularCom/Users.js b/src/moveToAngularCom/Users.js
new file mode 100644
index 00000000..fb5845d3
--- /dev/null
+++ b/src/moveToAngularCom/Users.js
@@ -0,0 +1,35 @@
+function Users(server, controlBar) {
+ this.server = server;
+ this.controlBar = controlBar;
+};
+
+extend(Users.prototype, {
+ 'fetchCurrentUser':function(callback) {
+ var self = this;
+ this.server.request("GET", "/account.json", {}, function(code, response){
+ self['current'] = response['user'];
+ callback(response['user']);
+ });
+ },
+
+ 'logout': function(callback) {
+ var self = this;
+ this.controlBar.logout(function(){
+ delete self['current'];
+ (callback||noop)();
+ });
+ },
+
+ 'login': function(callback) {
+ var self = this;
+ this.controlBar.login(function(){
+ self['fetchCurrentUser'](function(){
+ (callback||noop)();
+ });
+ });
+ },
+
+ 'notAuthorized': function(){
+ this.controlBar.notAuthorized();
+ }
+});
diff --git a/src/moveToAngularCom/directivesAngularCom.js b/src/moveToAngularCom/directivesAngularCom.js
new file mode 100644
index 00000000..84032bdd
--- /dev/null
+++ b/src/moveToAngularCom/directivesAngularCom.js
@@ -0,0 +1,29 @@
+
+angular.directive("auth", function(expression, element){
+ return function(){
+ if(expression == "eager") {
+ this.$users.fetchCurrent();
+ }
+ };
+});
+
+
+//expression = "book=Book:{year=2000}"
+angular.directive("entity", function(expression, element){
+ //parse expression, ignore element
+ var entityName; // "Book";
+ var instanceName; // "book";
+ var defaults; // {year: 2000};
+
+ parse(expression);
+
+ return function(){
+ this[entityName] = this.$datastore.entity(entityName, defaults);
+ this[instanceName] = this[entityName]();
+ this.$watch("$anchor."+instanceName, function(newAnchor){
+ this[instanceName] = this[entityName].get(this.$anchor[instanceName]);
+ });
+ };
+});
+
+
diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js
new file mode 100644
index 00000000..194a28d6
--- /dev/null
+++ b/src/scenario/DSL.js
@@ -0,0 +1,63 @@
+angular.scenario.dsl.browser = {
+ navigateTo: function(url){
+ $scenario.addStep('Navigate to: ' + url, function(done){
+ var self = this;
+ this.testFrame.load(function(){
+ self.testFrame.unbind();
+ self.testWindow = self.testFrame[0].contentWindow;
+ self.testDocument = jQuery(self.testWindow.document);
+ self.$browser = self.testWindow.angular.service.$browser();
+ self.notifyWhenNoOutstandingRequests = bind(self.$browser, self.$browser.notifyWhenNoOutstandingRequests);
+ self.notifyWhenNoOutstandingRequests(done);
+ });
+ if (this.testFrame.attr('src') == url) {
+ this.testFrame[0].contentWindow.location.reload();
+ } else {
+ this.testFrame.attr('src', url);
+ }
+ });
+ }
+};
+
+angular.scenario.dsl.input = function(selector) {
+ return {
+ enter: function(value){
+ $scenario.addStep("Set input text of '" + selector + "' to '" +
+ value + "'", function(done){
+ var input = this.testDocument.find('input[name=' + selector + ']');
+ input.val(value);
+ this.testWindow.angular.element(input[0]).trigger('change');
+ done();
+ });
+ },
+ select: function(value){
+ $scenario.addStep("Select radio '" + selector + "' to '" +
+ value + "'", function(done){
+ var input = this.testDocument.
+ find(':radio[name$=@' + selector + '][value=' + value + ']');
+ var event = this.testWindow.document.createEvent('MouseEvent');
+ event.initMouseEvent('click', true, true, this.testWindow, 0,0,0,0,0, false, false, false, false, 0, null);
+ input[0].dispatchEvent(event);
+ done();
+ });
+ }
+ };
+};
+
+angular.scenario.dsl.expect = {
+ repeater: function(selector) {
+ return {
+ count: {
+ toEqual: function(number) {
+ $scenario.addStep("Expect that there are " + number + " items in Repeater with selector '" + selector + "'", function(done) {
+ var items = this.testDocument.find(selector);
+ if (items.length != number) {
+ this.result.fail("Expected " + number + " but was " + items.length);
+ }
+ done();
+ });
+ }
+ }
+ };
+ }
+};
diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js
new file mode 100644
index 00000000..8e0cc909
--- /dev/null
+++ b/src/scenario/Runner.js
@@ -0,0 +1,161 @@
+angular['scenario'] = angular['scenario'] || (angular['scenario'] = {});
+angular.scenario['dsl'] = angular.scenario['dsl'] || (angular.scenario['dsl'] = {});
+
+angular.scenario.Runner = function(scope, jQuery){
+ var self = scope.$scenario = this;
+ this.scope = scope;
+ this.jQuery = jQuery;
+
+ var specs = this.specs = {};
+ var path = [];
+ this.scope.describe = function(name, body){
+ path.push(name);
+ body();
+ path.pop();
+ };
+ var beforeEach = noop;
+ var afterEach = noop;
+ this.scope.beforeEach = function(body) {
+ beforeEach = body;
+ };
+ this.scope.afterEach = function(body) {
+ afterEach = body;
+ };
+ this.scope.it = function(name, body) {
+ var specName = path.join(' ') + ': it ' + name;
+ self.currentSpec = specs[specName] = {
+ name: specName,
+ steps:[]
+ };
+ try {
+ beforeEach();
+ body();
+ } catch(err) {
+ self.addStep(err.message || 'ERROR', function(){
+ throw err;
+ });
+ } finally {
+ afterEach();
+ }
+ self.currentSpec = null;
+ };
+ this.logger = function returnNoop(){
+ return extend(returnNoop, {close:noop, fail:noop});;
+ };
+};
+
+angular.scenario.Runner.prototype = {
+ run: function(body){
+ var jQuery = this.jQuery;
+ body.append(
+ '<div id="runner">' +
+ '<div class="console"></div>' +
+ '</div>' +
+ '<div id="testView">' +
+ '<iframe></iframe>' +
+ '</div>');
+ var console = body.find('#runner .console');
+ console.find('li').live('click', function(){
+ jQuery(this).toggleClass('collapsed');
+ });
+ this.testFrame = body.find('#testView iframe');
+ function logger(parent) {
+ var container;
+ return function(type, text) {
+ if (!container) {
+ container = jQuery('<ul></ul>');
+ parent.append(container);
+ }
+ var element = jQuery('<li class="running '+type+'"><span></span></li>');
+ element.find('span').text(text);
+ container.append(element);
+ return extend(logger(element), {
+ close: function(){
+ element.removeClass('running');
+ if(!element.hasClass('fail'))
+ element.addClass('collapsed');
+ console.scrollTop(console[0].scrollHeight);
+ },
+ fail: function(){
+ element.removeClass('running');
+ var current = element;
+ while (current[0] != console[0]) {
+ if (current.is('li'))
+ current.addClass('fail');
+ current = current.parent();
+ }
+ }
+ });
+ };
+ }
+ this.logger = logger(console);
+ var specNames = [];
+ foreach(this.specs, function(spec, name){
+ specNames.push(name);
+ }, this);
+ specNames.sort();
+ var self = this;
+ function callback(){
+ var next = specNames.shift();
+ if(next) {
+ self.execute(next, callback);
+ }
+ };
+ callback();
+ },
+
+ addStep: function(name, step) {
+ this.currentSpec.steps.push({name:name, fn:step});
+ },
+
+ execute: function(name, callback) {
+ var spec = this.specs[name],
+ self = this,
+ result = {
+ passed: false,
+ failed: false,
+ finished: false,
+ fail: function(error) {
+ result.passed = false;
+ result.failed = true;
+ result.error = error;
+ result.log('fail', isString(error) ? error : toJson(error)).fail();
+ }
+ },
+ specThis = createScope({
+ result: result,
+ testFrame: this.testFrame,
+ testWindow: this.testWindow
+ }, angularService, {});
+ this.self = specThis;
+ var stepLogger = this.logger('spec', name);
+ spec.nextStepIndex = 0;
+ function done() {
+ result.finished = true;
+ stepLogger.close();
+ self.self = null;
+ (callback||noop).call(specThis);
+ }
+ function next(){
+ var step = spec.steps[spec.nextStepIndex];
+ (result.log || {close:noop}).close();
+ result.log = null;
+ if (step) {
+ spec.nextStepIndex ++;
+ result.log = stepLogger('step', step.name);
+ try {
+ step.fn.call(specThis, next);
+ } catch (e) {
+ console.error(e);
+ result.fail(e);
+ done();
+ }
+ } else {
+ result.passed = !result.failed;
+ done();
+ }
+ };
+ next();
+ return specThis;
+ }
+}; \ No newline at end of file
diff --git a/src/scenario/angular.prefix b/src/scenario/angular.prefix
new file mode 100644
index 00000000..5b44e17c
--- /dev/null
+++ b/src/scenario/angular.prefix
@@ -0,0 +1,30 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * 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.
+ */
+(function(window, document, previousOnLoad){
+ window.angular = {
+ scenario: {
+ dsl: window
+ }
+ };
+
diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix
new file mode 100644
index 00000000..fc861cbf
--- /dev/null
+++ b/src/scenario/angular.suffix
@@ -0,0 +1,11 @@
+
+ var $scenarioRunner = new angular.scenario.Runner(window, jQuery);
+
+ window.onload = function(){
+ try {
+ if (previousOnLoad) previousOnLoad();
+ } catch(e) {}
+ $scenarioRunner.run(jQuery(window.document.body));
+ };
+
+})(window, document, window.onload);
diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js
new file mode 100644
index 00000000..694d0e97
--- /dev/null
+++ b/src/scenario/bootstrap.js
@@ -0,0 +1,44 @@
+(function(onLoadDelegate){
+ var prefix = (function(){
+ var filename = /(.*\/)bootstrap.js(#(.*))?/;
+ var scripts = document.getElementsByTagName("script");
+ for(var j = 0; j < scripts.length; j++) {
+ var src = scripts[j].src;
+ if (src && src.match(filename)) {
+ var parts = src.match(filename);
+ return parts[1];
+ }
+ }
+ })();
+ function addScript(path) {
+ document.write('<script type="text/javascript" src="' + prefix + path + '"></script>');
+ }
+
+ function addCSS(path) {
+ document.write('<link rel="stylesheet" type="text/css" href="' + prefix + path + '"/>');
+ }
+
+ window.angular = {
+ scenario: {
+ dsl: window
+ }
+ };
+
+ window.onload = function(){
+ _.defer(function(){
+ $scenarioRunner.run(jQuery(window.document.body));
+ });
+ (onLoadDelegate||function(){})();
+ };
+ addCSS("../../css/angular-scenario.css");
+ addScript("../../lib/underscore/underscore.js");
+ addScript("../../lib/jquery/jquery-1.4.2.js");
+ addScript("Runner.js");
+ addScript("../Angular.js");
+ addScript("../JSON.js");
+ addScript("DSL.js");
+ document.write('<script type="text/javascript">' +
+ '$scenarioRunner = new angular.scenario.Runner(window, jQuery);' +
+ '</script>');
+})(window.onload);
+
diff --git a/src/services.js b/src/services.js
new file mode 100644
index 00000000..64f2ea4f
--- /dev/null
+++ b/src/services.js
@@ -0,0 +1,361 @@
+angularService("$window", bind(window, identity, window));
+angularService("$document", function(window){
+ return jqLite(window.document);
+}, {inject:['$window']});
+
+var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?(#(.*))?$/;
+var HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/;
+var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
+angularService("$location", function(browser){
+ var scope = this, location = {parse:parseUrl, toString:toString};
+ var lastHash, lastUrl;
+ function parseUrl(url){
+ if (isDefined(url)) {
+ var match = URL_MATCH.exec(url);
+ if (match) {
+ location.href = url;
+ location.protocol = match[1];
+ location.host = match[3] || '';
+ location.port = match[5] || DEFAULT_PORTS[location.href] || null;
+ location.path = match[6];
+ location.search = parseKeyValue(match[8]);
+ location.hash = match[9] || '';
+ if (location.hash)
+ location.hash = location.hash.substr(1);
+ parseHash(location.hash);
+ }
+ }
+ }
+ function parseHash(hash) {
+ var match = HASH_MATCH.exec(hash);
+ location.hashPath = match[1] || '';
+ location.hashSearch = parseKeyValue(match[3]);
+ lastHash = hash;
+ }
+ function toString() {
+ if (lastHash === location.hash) {
+ var hashKeyValue = toKeyValue(location.hashSearch),
+ hash = (location.hashPath ? location.hashPath : '') + (hashKeyValue ? '?' + hashKeyValue : ''),
+ url = location.href.split('#')[0] + '#' + (hash ? hash : '');
+ if (url !== location.href) parseUrl(url);
+ return url;
+ } else {
+ parseUrl(location.href.split('#')[0] + '#' + location.hash);
+ return toString();
+ }
+ }
+ browser.watchUrl(function(url){
+ parseUrl(url);
+ scope.$root.$eval();
+ });
+ parseUrl(browser.getUrl());
+ this.$onEval(PRIORITY_FIRST, function(){
+ if (location.hash != lastHash) {
+ parseHash(location.hash);
+ }
+ });
+ this.$onEval(PRIORITY_LAST, function(){
+ var url = toString();
+ if (lastUrl != url) {
+ browser.setUrl(url);
+ lastUrl = url;
+ }
+ });
+ return location;
+}, {inject: ['$browser']});
+
+angularService("$log", function($window){
+ var console = $window.console,
+ log = console && console.log || noop;
+ return {
+ log: log,
+ warn: console && console.warn || log,
+ info: console && console.info || log,
+ error: console && console.error || log
+ };
+}, {inject:['$window']});
+
+angularService("$hover", function(browser) {
+ var tooltip, self = this, error, width = 300, arrowWidth = 10;
+ browser.hover(function(element, show){
+ if (show && (error = element.attr(NG_EXCEPTION) || element.attr(NG_VALIDATION_ERROR))) {
+ if (!tooltip) {
+ tooltip = {
+ callout: jqLite('<div id="ng-callout"></div>'),
+ arrow: jqLite('<div></div>'),
+ title: jqLite('<div class="ng-title"></div>'),
+ content: jqLite('<div class="ng-content"></div>')
+ };
+ tooltip.callout.append(tooltip.arrow);
+ tooltip.callout.append(tooltip.title);
+ tooltip.callout.append(tooltip.content);
+ self.$browser.body.append(tooltip.callout);
+ }
+ var docRect = self.$browser.body[0].getBoundingClientRect(),
+ elementRect = element[0].getBoundingClientRect(),
+ leftSpace = docRect.right - elementRect.right - arrowWidth;
+ tooltip.title.text(element.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error...");
+ tooltip.content.text(error);
+ if (leftSpace < width) {
+ tooltip.arrow.addClass('ng-arrow-right');
+ tooltip.arrow.css({left: (width + 1)+'px'});
+ tooltip.callout.css({
+ position: 'fixed',
+ left: (elementRect.left - arrowWidth - width - 4) + "px",
+ top: (elementRect.top - 3) + "px",
+ width: width + "px"
+ });
+ } else {
+ tooltip.arrow.addClass('ng-arrow-left');
+ tooltip.callout.css({
+ position: 'fixed',
+ left: (elementRect.right + arrowWidth) + "px",
+ top: (elementRect.top - 3) + "px",
+ width: width + "px"
+ });
+ }
+ } else if (tooltip) {
+ tooltip.callout.remove();
+ tooltip = null;
+ }
+ });
+}, {inject:['$browser']});
+
+angularService("$invalidWidgets", function(){
+ var invalidWidgets = [];
+ invalidWidgets.markValid = function(element){
+ var index = indexOf(invalidWidgets, element);
+ if (index != -1)
+ invalidWidgets.splice(index, 1);
+ };
+ invalidWidgets.markInvalid = function(element){
+ var index = indexOf(invalidWidgets, element);
+ if (index === -1)
+ invalidWidgets.push(element);
+ };
+ invalidWidgets.visible = function() {
+ var count = 0;
+ foreach(invalidWidgets, function(widget){
+ count = count + (isVisible(widget) ? 1 : 0);
+ });
+ return count;
+ };
+ invalidWidgets.clearOrphans = function() {
+ for(var i = 0; i < invalidWidgets.length;) {
+ var widget = invalidWidgets[i];
+ if (isOrphan(widget[0])) {
+ invalidWidgets.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+ };
+ function isOrphan(widget) {
+ if (widget == window.document) return false;
+ var parent = widget.parentNode;
+ return !parent || isOrphan(parent);
+ }
+ return invalidWidgets;
+});
+
+function switchRouteMatcher(on, when, dstName) {
+ var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
+ params = [],
+ dst = {};
+ foreach(when.split(/\W/), function(param){
+ if (param) {
+ var paramRegExp = new RegExp(":" + param + "([\\W])");
+ if (regex.match(paramRegExp)) {
+ regex = regex.replace(paramRegExp, "([^\/]*)$1");
+ params.push(param);
+ }
+ }
+ });
+ var match = on.match(new RegExp(regex));
+ if (match) {
+ foreach(params, function(name, index){
+ dst[name] = match[index + 1];
+ });
+ if (dstName) this.$set(dstName, dst);
+ }
+ return match ? dst : null;
+}
+
+angularService('$route', function(location, params){
+ var routes = {},
+ onChange = [],
+ matcher = switchRouteMatcher,
+ parentScope = this,
+ dirty = 0,
+ $route = {
+ routes: routes,
+ onChange: bind(onChange, onChange.push),
+ when:function (path, params){
+ if (angular.isUndefined(path)) return routes;
+ var route = routes[path];
+ if (!route) route = routes[path] = {};
+ if (params) angular.extend(route, params);
+ dirty++;
+ return route;
+ }
+ };
+ function updateRoute(){
+ var childScope;
+ $route.current = null;
+ angular.foreach(routes, function(routeParams, route) {
+ if (!childScope) {
+ var pathParams = matcher(location.hashPath, route);
+ if (pathParams) {
+ childScope = angular.scope(parentScope);
+ $route.current = angular.extend({}, routeParams, {
+ scope: childScope,
+ params: angular.extend({}, location.hashSearch, pathParams)
+ });
+ }
+ }
+ });
+ angular.foreach(onChange, parentScope.$tryEval);
+ if (childScope) {
+ childScope.$become($route.current.controller);
+ parentScope.$tryEval(childScope.init);
+ }
+ }
+ this.$watch(function(){return dirty + location.hash;}, updateRoute);
+ return $route;
+}, {inject: ['$location']});
+
+angularService('$xhr', function($browser, $error, $log){
+ var self = this;
+ return function(method, url, post, callback){
+ if (isFunction(post)) {
+ callback = post;
+ post = null;
+ }
+ if (post && isObject(post)) {
+ post = toJson(post);
+ }
+ $browser.xhr(method, url, post, function(code, response){
+ try {
+ if (isString(response) && /^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
+ response = fromJson(response);
+ }
+ if (code == 200) {
+ callback(code, response);
+ } else {
+ $error(
+ {method: method, url:url, data:post, callback:callback},
+ {status: code, body:response});
+ }
+ } catch (e) {
+ $log.error(e);
+ } finally {
+ self.$eval();
+ }
+ });
+ };
+}, {inject:['$browser', '$xhr.error', '$log']});
+
+angularService('$xhr.error', function($log){
+ return function(request, response){
+ $log.error('ERROR: XHR: ' + request.url, request, response);
+ };
+}, {inject:['$log']});
+
+angularService('$xhr.bulk', function($xhr, $error, $log){
+ var requests = [],
+ scope = this;
+ function bulkXHR(method, url, post, callback) {
+ if (isFunction(post)) {
+ callback = post;
+ post = null;
+ }
+ var currentQueue;
+ foreach(bulkXHR.urls, function(queue){
+ if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
+ currentQueue = queue;
+ }
+ });
+ if (currentQueue) {
+ if (!currentQueue.requests) currentQueue.requests = [];
+ currentQueue.requests.push({method: method, url: url, data:post, callback:callback});
+ } else {
+ $xhr(method, url, post, callback);
+ }
+ }
+ bulkXHR.urls = {};
+ bulkXHR.flush = function(callback){
+ foreach(bulkXHR.urls, function(queue, url){
+ var currentRequests = queue.requests;
+ if (currentRequests && currentRequests.length) {
+ queue.requests = [];
+ queue.callbacks = [];
+ $xhr('POST', url, {requests:currentRequests}, function(code, response){
+ foreach(response, function(response, i){
+ try {
+ if (response.status == 200) {
+ (currentRequests[i].callback || noop)(response.status, response.response);
+ } else {
+ $error(currentRequests[i], response);
+ }
+ } catch(e) {
+ $log.error(e);
+ }
+ });
+ (callback || noop)();
+ });
+ scope.$eval();
+ }
+ });
+ };
+ this.$onEval(PRIORITY_LAST, bulkXHR.flush);
+ return bulkXHR;
+}, {inject:['$xhr', '$xhr.error', '$log']});
+
+angularService('$xhr.cache', function($xhr){
+ var inflight = {}, self = this;;
+ function cache(method, url, post, callback, cacheThenRetrieve){
+ if (isFunction(post)) {
+ callback = post;
+ post = null;
+ }
+ if (method == 'GET') {
+ var data;
+ if (data = cache.data[url]) {
+ callback(200, copy(data.value));
+ if (!cacheThenRetrieve)
+ return;
+ }
+
+ if (data = inflight[url]) {
+ data.callbacks.push(callback);
+ } else {
+ inflight[url] = {callbacks: [callback]};
+ cache.delegate(method, url, post, function(status, response){
+ if (status == 200)
+ cache.data[url] = { value: response };
+ var callbacks = inflight[url].callbacks;
+ delete inflight[url];
+ foreach(callbacks, function(callback){
+ try {
+ (callback||noop)(status, copy(response));
+ } catch(e) {
+ self.$log.error(e);
+ }
+ });
+ });
+ }
+
+ } else {
+ cache.data = {};
+ cache.delegate(method, url, post, callback);
+ }
+ }
+ cache.data = {};
+ cache.delegate = $xhr;
+ return cache;
+}, {inject:['$xhr.bulk']});
+
+angularService('$resource', function($xhr){
+ var resource = new ResourceFactory($xhr);
+ return bind(resource, resource.route);
+}, {inject: ['$xhr.cache']});
diff --git a/src/validators.js b/src/validators.js
new file mode 100644
index 00000000..5c7fc952
--- /dev/null
+++ b/src/validators.js
@@ -0,0 +1,132 @@
+foreach({
+ 'noop': function() { return null; },
+
+ 'regexp': function(value, regexp, msg) {
+ if (!value.match(regexp)) {
+ return msg ||
+ "Value does not match expected format " + regexp + ".";
+ } else {
+ return null;
+ }
+ },
+
+ 'number': function(value, min, max) {
+ var num = 1 * value;
+ if (num == value) {
+ if (typeof min != 'undefined' && num < min) {
+ return "Value can not be less than " + min + ".";
+ }
+ if (typeof min != 'undefined' && num > max) {
+ return "Value can not be greater than " + max + ".";
+ }
+ return null;
+ } else {
+ return "Not a number";
+ }
+ },
+
+ 'integer': function(value, min, max) {
+ var numberError = angularValidator['number'](value, min, max);
+ if (numberError) return numberError;
+ if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) {
+ return "Not a whole number";
+ }
+ return null;
+ },
+
+ 'date': function(value, min, max) {
+ if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) {
+ return null;
+ }
+ return "Value is not a date. (Expecting format: 12/31/2009).";
+ },
+
+ 'ssn': function(value) {
+ if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ return "SSN needs to be in 999-99-9999 format.";
+ },
+
+ 'email': function(value) {
+ if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
+ return null;
+ }
+ return "Email needs to be in username@host.com format.";
+ },
+
+ 'phone': function(value) {
+ if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) {
+ return null;
+ }
+ return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
+ },
+
+ 'url': function(value) {
+ if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
+ return null;
+ }
+ return "URL needs to be in http://server[:port]/path format.";
+ },
+
+ 'json': function(value) {
+ try {
+ fromJson(value);
+ return null;
+ } catch (e) {
+ return e.toString();
+ }
+ },
+
+ /*
+ * cache is attached to the element
+ * cache: {
+ * inputs : {
+ * 'user input': {
+ * response: server response,
+ * error: validation error
+ * },
+ * current: 'current input'
+ * }
+ *
+ */
+ 'asynchronous': function(input, asynchronousFn, updateFn) {
+ if (!input) return;
+ var scope = this;
+ var element = scope.$element;
+ var cache = element.data('$asyncValidator');
+ if (!cache) {
+ element.data('$asyncValidator', cache = {inputs:{}});
+ }
+
+ cache.current = input;
+
+ var inputState = cache.inputs[input];
+ if (!inputState) {
+ cache.inputs[input] = inputState = { inFlight: true };
+ scope.$invalidWidgets.markInvalid(scope.$element);
+ element.addClass('ng-input-indicator-wait');
+ asynchronousFn(input, function(error, data) {
+ inputState.response = data;
+ inputState.error = error;
+ inputState.inFlight = false;
+ if (cache.current == input) {
+ element.removeClass('ng-input-indicator-wait');
+ scope.$invalidWidgets.markValid(element);
+ }
+ element.data('$validate')();
+ scope.$root.$eval();
+ });
+ } else if (inputState.inFlight) {
+ // request in flight, mark widget invalid, but don't show it to user
+ scope.$invalidWidgets.markInvalid(scope.$element);
+ } else {
+ (updateFn||noop)(inputState.response);
+ }
+ return inputState.error;
+ }
+
+}, function(v,k) {angularValidator[k] = v;});
diff --git a/src/widgets.js b/src/widgets.js
new file mode 100644
index 00000000..efafa9c5
--- /dev/null
+++ b/src/widgets.js
@@ -0,0 +1,328 @@
+function modelAccessor(scope, element) {
+ var expr = element.attr('name');
+ if (!expr) throw "Required field 'name' not found.";
+ return {
+ get: function() {
+ return scope.$eval(expr);
+ },
+ set: function(value) {
+ if (value !== undefined) {
+ return scope.$tryEval(expr + '=' + toJson(value), element);
+ }
+ }
+ };
+}
+
+function modelFormattedAccessor(scope, element) {
+ var accessor = modelAccessor(scope, element),
+ formatterName = element.attr('ng-format') || NOOP,
+ formatter = angularFormatter(formatterName);
+ if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
+ return {
+ get: function() {
+ return formatter.format(accessor.get());
+ },
+ set: function(value) {
+ return accessor.set(formatter.parse(value));
+ }
+ };
+}
+
+function compileValidator(expr) {
+ return new Parser(expr).validator()();
+}
+
+function valueAccessor(scope, element) {
+ var validatorName = element.attr('ng-validate') || NOOP,
+ validator = compileValidator(validatorName),
+ requiredExpr = element.attr('ng-required'),
+ formatterName = element.attr('ng-format') || NOOP,
+ formatter = angularFormatter(formatterName),
+ format, parse, lastError, required;
+ invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
+ if (!validator) throw "Validator named '" + validatorName + "' not found.";
+ if (!formatter) throw "Formatter named '" + formatterName + "' not found.";
+ format = formatter.format;
+ parse = formatter.parse;
+ if (requiredExpr) {
+ scope.$watch(requiredExpr, function(newValue) {
+ required = newValue;
+ validate();
+ });
+ } else {
+ required = requiredExpr === '';
+ }
+
+ element.data('$validate', validate);
+ return {
+ get: function(){
+ if (lastError)
+ elementError(element, NG_VALIDATION_ERROR, null);
+ try {
+ var value = parse(element.val());
+ validate();
+ return value;
+ } catch (e) {
+ lastError = e;
+ elementError(element, NG_VALIDATION_ERROR, e);
+ }
+ },
+ set: function(value) {
+ var oldValue = element.val(),
+ newValue = format(value);
+ if (oldValue != newValue) {
+ element.val(newValue || ''); // needed for ie
+ }
+ validate();
+ }
+ };
+
+ function validate() {
+ var value = trim(element.val());
+ if (element[0].disabled || element[0].readOnly) {
+ elementError(element, NG_VALIDATION_ERROR, null);
+ invalidWidgets.markValid(element);
+ } else {
+ var error,
+ validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element});
+ error = required && !value ?
+ 'Required' :
+ (value ? validator(validateScope, value) : null);
+ elementError(element, NG_VALIDATION_ERROR, error);
+ lastError = error;
+ if (error) {
+ invalidWidgets.markInvalid(element);
+ } else {
+ invalidWidgets.markValid(element);
+ }
+ }
+ }
+}
+
+function checkedAccessor(scope, element) {
+ var domElement = element[0], elementValue = domElement.value;
+ return {
+ get: function(){
+ return !!domElement.checked;
+ },
+ set: function(value){
+ domElement.checked = toBoolean(value);
+ }
+ };
+}
+
+function radioAccessor(scope, element) {
+ var domElement = element[0];
+ return {
+ get: function(){
+ return domElement.checked ? domElement.value : null;
+ },
+ set: function(value){
+ domElement.checked = value == domElement.value;
+ }
+ };
+}
+
+function optionsAccessor(scope, element) {
+ var options = element[0].options;
+ return {
+ get: function(){
+ var values = [];
+ foreach(options, function(option){
+ if (option.selected) values.push(option.value);
+ });
+ return values;
+ },
+ set: function(values){
+ var keys = {};
+ foreach(values, function(value){ keys[value] = true; });
+ foreach(options, function(option){
+ option.selected = keys[option.value];
+ });
+ }
+ };
+}
+
+function noopAccessor() { return { get: noop, set: noop }; }
+
+var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()),
+ buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
+ INPUT_TYPE = {
+ 'text': textWidget,
+ 'textarea': textWidget,
+ 'hidden': textWidget,
+ 'password': textWidget,
+ 'button': buttonWidget,
+ 'submit': buttonWidget,
+ 'reset': buttonWidget,
+ 'image': buttonWidget,
+ 'checkbox': inputWidget('click', modelFormattedAccessor, checkedAccessor, initWidgetValue(false)),
+ 'radio': inputWidget('click', modelFormattedAccessor, radioAccessor, radioInit),
+ 'select-one': inputWidget('change', modelFormattedAccessor, valueAccessor, initWidgetValue(null)),
+ 'select-multiple': inputWidget('change', modelFormattedAccessor, optionsAccessor, initWidgetValue([]))
+// 'file': fileWidget???
+ };
+
+function initWidgetValue(initValue) {
+ return function (model, view) {
+ var value = view.get();
+ if (!value && isDefined(initValue)) {
+ value = copy(initValue);
+ }
+ if (isUndefined(model.get()) && isDefined(value)) {
+ model.set(value);
+ }
+ };
+}
+
+function radioInit(model, view, element) {
+ var modelValue = model.get(), viewValue = view.get(), input = element[0];
+ input.checked = false;
+ input.name = this.$id + '@' + input.name;
+ if (isUndefined(modelValue)) {
+ model.set(modelValue = null);
+ }
+ if (modelValue == null && viewValue !== null) {
+ model.set(viewValue);
+ }
+ view.set(modelValue);
+}
+
+function inputWidget(events, modelAccessor, viewAccessor, initFn) {
+ return function(element) {
+ var scope = this,
+ model = modelAccessor(scope, element),
+ view = viewAccessor(scope, element),
+ action = element.attr('ng-change') || '',
+ lastValue;
+ initFn.call(scope, model, view, element);
+ this.$eval(element.attr('ng-init')||'');
+ // Don't register a handler if we are a button (noopAccessor) and there is no action
+ if (action || modelAccessor !== noopAccessor) {
+ element.bind(events, function(){
+ model.set(view.get());
+ lastValue = model.get();
+ scope.$tryEval(action, element);
+ scope.$root.$eval();
+ // if we have noop initFn than we are just a button,
+ // therefore we want to prevent default action
+ return initFn != noop;
+ });
+ }
+ view.set(lastValue = model.get());
+ scope.$watch(model.get, function(value){
+ if (lastValue !== value) {
+ view.set(lastValue = value);
+ }
+ });
+ };
+}
+
+function inputWidgetSelector(element){
+ this.directives(true);
+ return INPUT_TYPE[lowercase(element[0].type)] || noop;
+}
+
+angularWidget('INPUT', inputWidgetSelector);
+angularWidget('TEXTAREA', inputWidgetSelector);
+angularWidget('BUTTON', inputWidgetSelector);
+angularWidget('SELECT', function(element){
+ this.descend(true);
+ return inputWidgetSelector.call(this, element);
+});
+
+
+angularWidget('NG:INCLUDE', function(element){
+ var compiler = this,
+ srcExp = element.attr("src"),
+ scopeExp = element.attr("scope") || '';
+ if (element[0]['ng-compiled']) {
+ this.descend(true);
+ this.directives(true);
+ } else {
+ element[0]['ng-compiled'] = true;
+ return function(element){
+ var scope = this, childScope;
+ var changeCounter = 0;
+ function incrementChange(){ changeCounter++;}
+ this.$watch(srcExp, incrementChange);
+ this.$watch(scopeExp, incrementChange);
+ scope.$onEval(function(){
+ if (childScope) childScope.$eval();
+ });
+ this.$watch(function(){return changeCounter;}, function(){
+ var src = this.$eval(srcExp),
+ useScope = this.$eval(scopeExp);
+ if (src) {
+ scope.$xhr.cache('GET', src, function(code, response){
+ element.html(response);
+ childScope = useScope || createScope(scope);
+ compiler.compile(element)(element, childScope);
+ childScope.$init();
+ });
+ }
+ });
+ };
+ }
+});
+
+var ngSwitch = angularWidget('NG:SWITCH', function (element){
+ var compiler = this,
+ watchExpr = element.attr("on"),
+ usingExpr = (element.attr("using") || 'equals'),
+ usingExprParams = usingExpr.split(":"),
+ usingFn = ngSwitch[usingExprParams.shift()],
+ changeExpr = element.attr('change') || '',
+ cases = [];
+ if (!usingFn) throw "Using expression '" + usingExpr + "' unknown.";
+ eachNode(element, function(caseElement){
+ var when = caseElement.attr('ng-switch-when');
+ if (when) {
+ cases.push({
+ when: function(scope, value){
+ var args = [value, when];
+ foreach(usingExprParams, function(arg){
+ args.push(arg);
+ });
+ return usingFn.apply(scope, args);
+ },
+ change: changeExpr,
+ element: caseElement,
+ template: compiler.compile(caseElement)
+ });
+ }
+ });
+
+ // this needs to be here for IE
+ foreach(cases, function(_case){
+ _case.element.remove();
+ });
+
+ element.html('');
+ return function(element){
+ var scope = this, childScope;
+ this.$watch(watchExpr, function(value){
+ element.html('');
+ childScope = createScope(scope);
+ foreach(cases, function(switchCase){
+ if (switchCase.when(childScope, value)) {
+ var caseElement = switchCase.element.clone();
+ element.append(caseElement);
+ childScope.$tryEval(switchCase.change, element);
+ switchCase.template(caseElement, childScope);
+ if (scope.$invalidWidgets)
+ scope.$invalidWidgets.clearOrphans();
+ childScope.$init();
+ }
+ });
+ });
+ scope.$onEval(function(){
+ if (childScope) childScope.$eval();
+ });
+ };
+}, {
+ equals: function(on, when) {
+ return on == when;
+ },
+ route: switchRouteMatcher
+});
diff --git a/test.sh b/test.sh
new file mode 100755
index 00000000..a1717861
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,2 @@
+java -jar lib/jstestdriver/JsTestDriver.jar --tests all
+# java -jar lib/jstestdriver/JsTestDriver.jar --tests all --config jsTestDriver-jquery.conf
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
new file mode 100644
index 00000000..de724f03
--- /dev/null
+++ b/test/AngularSpec.js
@@ -0,0 +1,52 @@
+describe('Angular', function(){
+ xit('should fire on updateEvents', function(){
+ var onUpdateView = jasmine.createSpy();
+ var scope = angular.compile("<div></div>", { onUpdateView: onUpdateView });
+ expect(onUpdateView).wasNotCalled();
+ scope.$init();
+ scope.$eval();
+ expect(onUpdateView).wasCalled();
+ });
+});
+
+describe("copy", function(){
+ it("should return same object", function (){
+ var obj = {};
+ var arr = [];
+ assertSame(obj, copy({}, obj));
+ assertSame(arr, copy([], arr));
+ });
+
+ it("should copy array", function(){
+ var src = [1, {name:"value"}];
+ var dst = [{key:"v"}];
+ assertSame(dst, copy(src, dst));
+ assertEquals([1, {name:"value"}], dst);
+ assertEquals({name:"value"}, dst[1]);
+ assertNotSame(src[1], dst[1]);
+ });
+
+ it('should copy empty array', function() {
+ var src = [];
+ var dst = [{key: "v"}];
+ assertEquals([], copy(src, dst));
+ assertEquals([], dst);
+ });
+
+ it("should copy object", function(){
+ var src = {a:{name:"value"}};
+ var dst = {b:{key:"v"}};
+ assertSame(dst, copy(src, dst));
+ assertEquals({a:{name:"value"}}, dst);
+ assertEquals(src.a, dst.a);
+ assertNotSame(src.a, dst.a);
+ });
+
+ it("should copy primitives", function(){
+ expect(copy(null)).toEqual(null);
+ expect(copy('')).toEqual('');
+ expect(copy(123)).toEqual(123);
+ expect(copy([{key:null}])).toEqual([{key:null}]);
+ });
+
+});
diff --git a/test/ApiTest.js b/test/ApiTest.js
new file mode 100644
index 00000000..4035cdbb
--- /dev/null
+++ b/test/ApiTest.js
@@ -0,0 +1,256 @@
+ApiTest = TestCase("ApiTest");
+
+ApiTest.prototype.testItShouldReturnTypeOf = function (){
+ assertEquals("undefined", angular.Object.typeOf(undefined));
+ assertEquals("null", angular.Object.typeOf(null));
+ assertEquals("object", angular.Collection.typeOf({}));
+ assertEquals("array", angular.Array.typeOf([]));
+ assertEquals("string", angular.Object.typeOf(""));
+ assertEquals("date", angular.Object.typeOf(new Date()));
+ assertEquals("element", angular.Object.typeOf(document.body));
+ assertEquals("function", angular.Object.typeOf(function(){}));
+};
+
+ApiTest.prototype.testItShouldReturnSize = function(){
+ assertEquals(0, angular.Collection.size({}));
+ assertEquals(1, angular.Collection.size({a:"b"}));
+ assertEquals(0, angular.Object.size({}));
+ assertEquals(1, angular.Array.size([0]));
+};
+
+ApiTest.prototype.testIncludeIf = function() {
+ var array = [];
+ var obj = {};
+
+ angular.Array.includeIf(array, obj, true);
+ angular.Array.includeIf(array, obj, true);
+ assertTrue(includes(array, obj));
+ assertEquals(1, array.length);
+
+ angular.Array.includeIf(array, obj, false);
+ assertFalse(includes(array, obj));
+ assertEquals(0, array.length);
+
+ angular.Array.includeIf(array, obj, 'x');
+ assertTrue(includes(array, obj));
+ assertEquals(1, array.length);
+ angular.Array.includeIf(array, obj, '');
+ assertFalse(includes(array, obj));
+ assertEquals(0, array.length);
+};
+
+ApiTest.prototype.testSum = function(){
+ assertEquals(3, angular.Array.sum([{a:"1"}, {a:"2"}], 'a'));
+};
+
+ApiTest.prototype.testSumContainingNaN = function(){
+ assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], 'a'));
+ assertEquals(1, angular.Array.sum([{a:1}, {a:Number.NaN}], function($){return $.a;}));
+};
+
+ApiTest.prototype.testInclude = function(){
+ assertTrue(angular.Array.include(['a'], 'a'));
+ assertTrue(angular.Array.include(['a', 'b'], 'a'));
+ assertTrue(!angular.Array.include(['c'], 'a'));
+ assertTrue(!angular.Array.include(['c', 'b'], 'a'));
+};
+
+ApiTest.prototype.testIndex = function(){
+ assertEquals(angular.Array.indexOf(['a'], 'a'), 0);
+ assertEquals(angular.Array.indexOf(['a', 'b'], 'a'), 0);
+ assertEquals(angular.Array.indexOf(['b', 'a'], 'a'), 1);
+ assertEquals(angular.Array.indexOf(['b', 'b'],'x'), -1);
+};
+
+ApiTest.prototype.testRemove = function(){
+ var items = ['a', 'b', 'c'];
+ assertEquals(angular.Array.remove(items, 'q'), 'q');
+ assertEquals(items.length, 3);
+
+ assertEquals(angular.Array.remove(items, 'b'), 'b');
+ assertEquals(items.length, 2);
+
+ assertEquals(angular.Array.remove(items, 'a'), 'a');
+ assertEquals(items.length, 1);
+
+ assertEquals(angular.Array.remove(items, 'c'), 'c');
+ assertEquals(items.length, 0);
+
+ assertEquals(angular.Array.remove(items, 'q'), 'q');
+ assertEquals(items.length, 0);
+};
+
+ApiTest.prototype.testFindById = function() {
+ var items = [{$id:1}, {$id:2}, {$id:3}];
+ assertNull(angular.Array.findById(items, 0));
+ assertEquals(items[0], angular.Array.findById(items, 1));
+ assertEquals(items[1], angular.Array.findById(items, 2));
+ assertEquals(items[2], angular.Array.findById(items, 3));
+};
+
+ApiTest.prototype.testFilter = function() {
+ var items = ["MIsKO", {name:"shyam"}, ["adam"], 1234];
+ assertEquals(4, angular.Array.filter(items, "").length);
+ assertEquals(4, angular.Array.filter(items, undefined).length);
+
+ assertEquals(1, angular.Array.filter(items, 'iSk').length);
+ assertEquals("MIsKO", angular.Array.filter(items, 'isk')[0]);
+
+ assertEquals(1, angular.Array.filter(items, 'yam').length);
+ assertEquals(items[1], angular.Array.filter(items, 'yam')[0]);
+
+ assertEquals(1, angular.Array.filter(items, 'da').length);
+ assertEquals(items[2], angular.Array.filter(items, 'da')[0]);
+
+ assertEquals(1, angular.Array.filter(items, '34').length);
+ assertEquals(1234, angular.Array.filter(items, '34')[0]);
+
+ assertEquals(0, angular.Array.filter(items, "I don't exist").length);
+};
+
+ApiTest.prototype.testShouldNotFilterOnSystemData = function() {
+ assertEquals("", "".charAt(0)); // assumption
+ var items = [{$name:"misko"}];
+ assertEquals(0, angular.Array.filter(items, "misko").length);
+};
+
+ApiTest.prototype.testFilterOnSpecificProperty = function() {
+ var items = [{ignore:"a", name:"a"}, {ignore:"a", name:"abc"}];
+ assertEquals(2, angular.Array.filter(items, {}).length);
+
+ assertEquals(2, angular.Array.filter(items, {name:'a'}).length);
+
+ assertEquals(1, angular.Array.filter(items, {name:'b'}).length);
+ assertEquals("abc", angular.Array.filter(items, {name:'b'})[0].name);
+};
+
+ApiTest.prototype.testFilterOnFunction = function() {
+ var items = [{name:"a"}, {name:"abc", done:true}];
+ assertEquals(1, angular.Array.filter(items, function(i){return i.done;}).length);
+};
+
+ApiTest.prototype.testFilterIsAndFunction = function() {
+ var items = [{first:"misko", last:"hevery"},
+ {first:"adam", last:"abrons"}];
+
+ assertEquals(2, angular.Array.filter(items, {first:'', last:''}).length);
+ assertEquals(1, angular.Array.filter(items, {first:'', last:'hevery'}).length);
+ assertEquals(0, angular.Array.filter(items, {first:'adam', last:'hevery'}).length);
+ assertEquals(1, angular.Array.filter(items, {first:'misko', last:'hevery'}).length);
+ assertEquals(items[0], angular.Array.filter(items, {first:'misko', last:'hevery'})[0]);
+};
+
+ApiTest.prototype.testFilterNot = function() {
+ var items = ["misko", "adam"];
+
+ assertEquals(1, angular.Array.filter(items, '!isk').length);
+ assertEquals(items[1], angular.Array.filter(items, '!isk')[0]);
+};
+
+ApiTest.prototype.testAdd = function() {
+ var add = angular.Array.add;
+ assertJsonEquals([{}, "a"], add(add([]),"a"));
+};
+
+ApiTest.prototype.testCount = function() {
+ var array = [{name:'a'},{name:'b'},{name:''}];
+ var obj = {};
+
+ assertEquals(3, angular.Array.count(array));
+ assertEquals(2, angular.Array.count(array, 'name'));
+ assertEquals(1, angular.Array.count(array, 'name=="a"'));
+};
+
+ApiTest.prototype.testFind = function() {
+ var array = [{name:'a'},{name:'b'},{name:''}];
+ var obj = {};
+
+ assertEquals(undefined, angular.Array.find(array, 'false'));
+ assertEquals('default', angular.Array.find(array, 'false', 'default'));
+ assertEquals('a', angular.Array.find(array, 'name == "a"').name);
+ assertEquals('', angular.Array.find(array, 'name == ""').name);
+};
+
+ApiTest.prototype.testItShouldSortArray = function() {
+ assertEquals([2,15], angular.Array.orderBy([15,2]));
+ assertEquals(["a","B", "c"], angular.Array.orderBy(["c","B", "a"]));
+ assertEquals([15,"2"], angular.Array.orderBy([15,"2"]));
+ assertEquals(["15","2"], angular.Array.orderBy(["15","2"]));
+ assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a'));
+ assertJsonEquals([{a:2},{a:15}], angular.Array.orderBy([{a:15},{a:2}], 'a', "F"));
+};
+
+ApiTest.prototype.testItShouldSortArrayInReverse = function() {
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', true));
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "T"));
+ assertJsonEquals([{a:15},{a:2}], angular.Array.orderBy([{a:15},{a:2}], 'a', "reverse"));
+};
+
+ApiTest.prototype.testItShouldSortArrayByPredicate = function() {
+ assertJsonEquals([{a:2, b:1},{a:15, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b']));
+ assertJsonEquals([{a:2, b:1},{a:15, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a']));
+ assertJsonEquals([{a:15, b:1},{a:2, b:1}],
+ angular.Array.orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a']));
+};
+
+ApiTest.prototype.testQuoteString = function(){
+ assertEquals(angular.String.quote('a'), '"a"');
+ assertEquals(angular.String.quote('\\'), '"\\\\"');
+ assertEquals(angular.String.quote("'a'"), '"\'a\'"');
+ assertEquals(angular.String.quote('"a"'), '"\\"a\\""');
+ assertEquals(angular.String.quote('\n\f\r\t'), '"\\n\\f\\r\\t"');
+};
+
+ApiTest.prototype.testQuoteStringBug = function(){
+ assertEquals('"7\\\\\\\"7"', angular.String.quote("7\\\"7"));
+};
+
+ApiTest.prototype.testQuoteUnicode = function(){
+ assertEquals('"abc\\u00a0def"', angular.String.quoteUnicode('abc\u00A0def'));
+};
+
+ApiTest.prototype.testMerge = function() {
+ var array = [{name:"misko"}];
+ angular.Array.merge(array, 0, {name:"", email:"email1"});
+ angular.Array.merge(array, 1, {name:"adam", email:"email2"});
+ assertJsonEquals([{"email":"email1","name":"misko"},{"email":"email2","name":"adam"}], array);
+};
+
+ApiTest.prototype.testOrderByToggle = function() {
+ var orderByToggle = angular.Array.orderByToggle;
+ var predicate = [];
+ assertEquals(['+a'], orderByToggle(predicate, 'a'));
+ assertEquals(['-a'], orderByToggle(predicate, 'a'));
+
+ assertEquals(['-a', '-b'], orderByToggle(['-b', 'a'], 'a'));
+};
+
+ApiTest.prototype.testOrderByToggle = function() {
+ var orderByDirection = angular.Array.orderByDirection;
+ assertEquals("", orderByDirection(['+a','b'], 'x'));
+ assertEquals("", orderByDirection(['+a','b'], 'b'));
+ assertEquals('ng-ascend', orderByDirection(['a','b'], 'a'));
+ assertEquals('ng-ascend', orderByDirection(['+a','b'], 'a'));
+ assertEquals('ng-descend', orderByDirection(['-a','b'], 'a'));
+ assertEquals('up', orderByDirection(['+a','b'], 'a', 'up', 'down'));
+ assertEquals('down', orderByDirection(['-a','b'], 'a', 'up', 'down'));
+};
+
+ApiTest.prototype.testDateToUTC = function(){
+ var date = new Date("Sep 10 2003 13:02:03 GMT");
+ assertEquals("date", angular.Object.typeOf(date));
+ assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
+};
+
+ApiTest.prototype.testStringFromUTC = function(){
+ var date = angular.String.toDate("2003-09-10T13:02:03Z");
+ assertEquals("date", angular.Object.typeOf(date));
+ assertEquals("2003-09-10T13:02:03Z", angular.Date.toString(date));
+ assertEquals("str", angular.String.toDate("str"));
+};
+
+ApiTest.prototype.testObjectShouldHaveExtend = function(){
+ assertEquals({a:1, b:2}, angular.Object.extend({a:1}, {b:2}));
+};
diff --git a/test/BinderTest.js b/test/BinderTest.js
new file mode 100644
index 00000000..ecdd506f
--- /dev/null
+++ b/test/BinderTest.js
@@ -0,0 +1,676 @@
+BinderTest = TestCase('BinderTest');
+
+BinderTest.prototype.setUp = function(){
+ var self = this;
+ this.compile = function(html, initialScope, config) {
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ var element = self.element = jqLite(html);
+ var scope = compiler.compile(element)(element);
+ extend(scope, initialScope);
+ scope.$init();
+ return {node:element, scope:scope};
+ };
+ this.compileToHtml = function (content) {
+ return sortedHtml(this.compile(content).node);
+ };
+};
+
+BinderTest.prototype.tearDown = function(){
+ if (this.element && this.element.dealoc) {
+ this.element.dealoc();
+ }
+};
+
+
+BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){
+ var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}});
+ state.scope.$eval();
+ assertEquals('abc', state.scope.model.price);
+};
+
+BinderTest.prototype.testChangingTextareaUpdatesModel = function(){
+ var c = this.compile('<textarea name="model.note">abc</textarea>');
+ c.scope.$eval();
+ assertEquals(c.scope.model.note, 'abc');
+};
+
+BinderTest.prototype.testChangingRadioUpdatesModel = function(){
+ var c = this.compile('<input type="radio" name="model.price" value="A" checked>' +
+ '<input type="radio" name="model.price" value="B">');
+ c.scope.$eval();
+ assertEquals(c.scope.model.price, 'A');
+};
+
+BinderTest.prototype.testChangingCheckboxUpdatesModel = function(){
+ var form = this.compile('<input type="checkbox" name="model.price" value="true" checked ng-format="boolean"/>');
+ assertEquals(true, form.scope.model.price);
+};
+
+BinderTest.prototype.testBindUpdate = function() {
+ var c = this.compile('<div ng-eval="a=123"/>');
+ assertEquals(123, c.scope.$get('a'));
+};
+
+BinderTest.prototype.testChangingSelectNonSelectedUpdatesModel = function(){
+ var form = this.compile('<select name="model.price"><option value="A">A</option><option value="B">B</option></select>');
+ assertEquals('A', form.scope.model.price);
+};
+
+BinderTest.prototype.testChangingMultiselectUpdatesModel = function(){
+ var form = this.compile('<select name="Invoice.options" multiple="multiple">' +
+ '<option value="A" selected>Gift wrap</option>' +
+ '<option value="B" selected>Extra padding</option>' +
+ '<option value="C">Expedite</option>' +
+ '</select>');
+ assertJsonEquals(["A", "B"], form.scope.$get('Invoice').options);
+};
+
+BinderTest.prototype.testChangingSelectSelectedUpdatesModel = function(){
+ var form = this.compile('<select name="model.price"><option>A</option><option selected value="b">B</option></select>');
+ assertEquals(form.scope.model.price, 'b');
+};
+
+BinderTest.prototype.testExecuteInitialization = function() {
+ var c = this.compile('<div ng-init="a=123">');
+ assertEquals(c.scope.$get('a'), 123);
+};
+
+BinderTest.prototype.testExecuteInitializationStatements = function() {
+ var c = this.compile('<div ng-init="a=123;b=345">');
+ assertEquals(c.scope.$get('a'), 123);
+ assertEquals(c.scope.$get('b'), 345);
+};
+
+BinderTest.prototype.testApplyTextBindings = function(){
+ var form = this.compile('<div ng-bind="model.a">x</div>');
+ form.scope.$set('model', {a:123});
+ form.scope.$eval();
+ assertEquals('123', form.node.text());
+};
+
+BinderTest.prototype.testReplaceBindingInTextWithSpan = function() {
+ assertEquals(this.compileToHtml("<b>a{{b}}c</b>"), '<b>a<span ng-bind="b"></span>c</b>');
+ assertEquals(this.compileToHtml("<b>{{b}}</b>"), '<b><span ng-bind="b"></span></b>');
+};
+
+BinderTest.prototype.testBindingSpaceConfusesIE = function() {
+ if (!msie) return;
+ var span = document.createElement("span");
+ span.innerHTML = '&nbsp;';
+ var nbsp = span.firstChild.nodeValue;
+ assertEquals(
+ '<b><span ng-bind="a"></span><span>'+nbsp+'</span><span ng-bind="b"></span></b>',
+ this.compileToHtml("<b>{{a}} {{b}}</b>"));
+ assertEquals(
+ '<b><span ng-bind="A"></span><span>'+nbsp+'x </span><span ng-bind="B"></span><span>'+nbsp+'(</span><span ng-bind="C"></span>)</b>',
+ this.compileToHtml("<b>{{A}} x {{B}} ({{C}})</b>"));
+};
+
+BinderTest.prototype.testBindingOfAttributes = function() {
+ var c = this.compile("<a href='http://s/a{{b}}c' foo='x'></a>");
+ var attrbinding = c.node.attr("ng-bind-attr");
+ var bindings = fromJson(attrbinding);
+ assertEquals("http://s/a{{b}}c", decodeURI(bindings.href));
+ assertTrue(!bindings.foo);
+};
+
+BinderTest.prototype.testMarkMultipleAttributes = function() {
+ var c = this.compile('<a href="http://s/a{{b}}c" foo="{{d}}"></a>');
+ var attrbinding = c.node.attr("ng-bind-attr");
+ var bindings = fromJson(attrbinding);
+ assertEquals(bindings.foo, "{{d}}");
+ assertEquals(decodeURI(bindings.href), "http://s/a{{b}}c");
+};
+
+BinderTest.prototype.testAttributesNoneBound = function() {
+ var c = this.compile("<a href='abc' foo='def'></a>");
+ var a = c.node;
+ assertEquals(a[0].nodeName, "A");
+ assertTrue(!a.attr("ng-bind-attr"));
+};
+
+BinderTest.prototype.testExistingAttrbindingIsAppended = function() {
+ var c = this.compile("<a href='http://s/{{abc}}' ng-bind-attr='{\"b\":\"{{def}}\"}'></a>");
+ var a = c.node;
+ assertEquals('{"b":"{{def}}","href":"http://s/{{abc}}"}', a.attr('ng-bind-attr'));
+};
+
+BinderTest.prototype.testAttributesAreEvaluated = function(){
+ var c = this.compile('<a ng-bind-attr=\'{"a":"a", "b":"a+b={{a+b}}"}\'></a>');
+ var binder = c.binder, form = c.node;
+ c.scope.$eval('a=1;b=2');
+ c.scope.$eval();
+ var a = c.node;
+ assertEquals(a.attr('a'), 'a');
+ assertEquals(a.attr('b'), 'a+b=3');
+};
+
+BinderTest.prototype.testInputTypeButtonActionExecutesInScope = function(){
+ var savedCalled = false;
+ var c = this.compile('<input type="button" ng-click="person.save()" value="Apply">');
+ c.scope.$set("person.save", function(){
+ savedCalled = true;
+ });
+ c.node.trigger('click');
+ assertTrue(savedCalled);
+};
+
+BinderTest.prototype.testInputTypeButtonActionExecutesInScope2 = function(){
+ var log = "";
+ var c = this.compile('<input type="image" ng-click="action()">');
+ c.scope.$set("action", function(){
+ log += 'click;';
+ });
+ expect(log).toEqual('');
+ c.node.trigger('click');
+ expect(log).toEqual('click;');
+};
+
+BinderTest.prototype.testButtonElementActionExecutesInScope = function(){
+ var savedCalled = false;
+ var c = this.compile('<button ng-click="person.save()">Apply</button>');
+ c.scope.$set("person.save", function(){
+ savedCalled = true;
+ });
+ c.node.trigger('click');
+ assertTrue(savedCalled);
+};
+
+BinderTest.prototype.testRepeaterUpdateBindings = function(){
+ var a = this.compile('<ul><LI ng-repeat="item in model.items" ng-bind="item.a"/></ul>');
+ var form = a.node;
+ var items = [{a:"A"}, {a:"B"}];
+ a.scope.$set('model', {items:items});
+
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.unshift({a:'C'});
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">C</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="2">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.shift();
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind="item.a" ng-repeat-index="0">A</li>' +
+ '<li ng-bind="item.a" ng-repeat-index="1">B</li>' +
+ '</ul>', sortedHtml(form));
+
+ items.shift();
+ items.shift();
+ a.scope.$eval();
+};
+
+BinderTest.prototype.testRepeaterContentDoesNotBind = function(){
+ var a = this.compile('<ul><LI ng-repeat="item in model.items"><span ng-bind="item.a"></span></li></ul>');
+ a.scope.$set('model', {items:[{a:"A"}]});
+ a.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-repeat-index="0"><span ng-bind="item.a">A</span></li>' +
+ '</ul>', sortedHtml(a.node));
+};
+
+BinderTest.prototype.testExpandEntityTag = function(){
+ assertEquals(
+ '<div ng-entity="Person" ng-watch="$anchor.a:1"></div>',
+ this.compileToHtml('<div ng-entity="Person" ng-watch="$anchor.a:1"/>'));
+};
+
+BinderTest.prototype.testDoNotOverwriteCustomAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save" action="foo();">');
+ assertTrue(html.indexOf('action="foo();"') > 0 );
+};
+
+BinderTest.prototype.testRepeaterAdd = function(){
+ var c = this.compile('<div><input type="text" name="item.x" ng-repeat="item in items"></div>');
+ var doc = c.node;
+ c.scope.$set('items', [{x:'a'}, {x:'b'}]);
+ c.scope.$eval();
+ var first = childNode(c.node, 1);
+ var second = childNode(c.node, 2);
+ assertEquals('a', first.val());
+ assertEquals('b', second.val());
+
+ first.val('ABC');
+ first.trigger('keyup');
+ assertEquals(c.scope.items[0].x, 'ABC');
+};
+
+BinderTest.prototype.testItShouldRemoveExtraChildrenWhenIteratingOverHash = function(){
+ var c = this.compile('<div><div ng-repeat="i in items">{{i}}</div></div>');
+ var items = {};
+ c.scope.$set("items", items);
+
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(0);
+
+ items.name = "misko";
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(1);
+
+ delete items.name;
+ c.scope.$eval();
+ expect(c.node[0].childNodes.length - 1).toEqual(0);
+};
+
+BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
+ var a = this.compile('<div>{{error.throw()}}</div>');
+ var doc = a.node;
+
+ a.scope.$set('error.throw', function(){throw "ErrorMsg1";});
+ a.scope.$eval();
+ var span = childNode(doc, 0);
+ assertTrue(span.hasClass('ng-exception'));
+ assertEquals('ErrorMsg1', fromJson(span.text()));
+ assertEquals('"ErrorMsg1"', span.attr('ng-exception'));
+
+ a.scope.$set('error.throw', function(){throw "MyError";});
+ a.scope.$eval();
+ span = childNode(doc, 0);
+ assertTrue(span.hasClass('ng-exception'));
+ assertTrue(span.text(), span.text().match('MyError') !== null);
+ assertEquals('"MyError"', span.attr('ng-exception'));
+
+ a.scope.$set('error.throw', function(){return "ok";});
+ a.scope.$eval();
+ assertFalse(span.hasClass('ng-exception'));
+ assertEquals('ok', span.text());
+ assertEquals(null, span.attr('ng-exception'));
+};
+
+BinderTest.prototype.testIfAttrBindingThrowsErrorDecorateTheAttribute = function(){
+ var a = this.compile('<div attr="before {{error.throw()}} after"></div>');
+ var doc = a.node;
+
+ a.scope.$set('error.throw', function(){throw "ErrorMsg";});
+ a.scope.$eval();
+ assertTrue('ng-exception', doc.hasClass('ng-exception'));
+ assertEquals('"ErrorMsg"', doc.attr('ng-exception'));
+ assertEquals('before "ErrorMsg" after', doc.attr('attr'));
+
+ a.scope.$set('error.throw', function(){ return 'X';});
+ a.scope.$eval();
+ assertFalse('!ng-exception', doc.hasClass('ng-exception'));
+ assertEquals('before X after', doc.attr('attr'));
+ assertEquals(null, doc.attr('ng-exception'));
+
+};
+
+BinderTest.prototype.testNestedRepeater = function() {
+ var a = this.compile('<div><div ng-repeat="m in model" name="{{m.name}}">' +
+ '<ul name="{{i}}" ng-repeat="i in m.item"></ul>' +
+ '</div></div>');
+
+ a.scope.$set('model', [{name:'a', item:['a1', 'a2']}, {name:'b', item:['b1', 'b2']}]);
+ a.scope.$eval();
+
+ assertEquals('<div>'+
+ '<#comment></#comment>'+
+ '<div name="a" ng-bind-attr="{"name":"{{m.name}}"}" ng-repeat-index="0">'+
+ '<#comment></#comment>'+
+ '<ul name="a1" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="0"></ul>'+
+ '<ul name="a2" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="1"></ul>'+
+ '</div>'+
+ '<div name="b" ng-bind-attr="{"name":"{{m.name}}"}" ng-repeat-index="1">'+
+ '<#comment></#comment>'+
+ '<ul name="b1" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="0"></ul>'+
+ '<ul name="b2" ng-bind-attr="{"name":"{{i}}"}" ng-repeat-index="1"></ul>'+
+ '</div></div>', sortedHtml(a.node));
+};
+
+BinderTest.prototype.testHideBindingExpression = function() {
+ var a = this.compile('<div ng-hide="hidden == 3"/>');
+
+ a.scope.$set('hidden', 3);
+ a.scope.$eval();
+
+ assertHidden(a.node);
+
+ a.scope.$set('hidden', 2);
+ a.scope.$eval();
+
+ assertVisible(a.node);
+};
+
+BinderTest.prototype.testHideBinding = function() {
+ var c = this.compile('<div ng-hide="hidden"/>');
+
+ c.scope.$set('hidden', 'true');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+
+ c.scope.$set('hidden', 'false');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+
+ c.scope.$set('hidden', '');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+};
+
+BinderTest.prototype.testShowBinding = function() {
+ var c = this.compile('<div ng-show="show"/>');
+
+ c.scope.$set('show', 'true');
+ c.scope.$eval();
+
+ assertVisible(c.node);
+
+ c.scope.$set('show', 'false');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+
+ c.scope.$set('show', '');
+ c.scope.$eval();
+
+ assertHidden(c.node);
+};
+
+BinderTest.prototype.testBindClassUndefined = function() {
+ var doc = this.compile('<div ng-class="undefined"/>');
+ doc.scope.$eval();
+
+ assertEquals(
+ '<div class="undefined" ng-class="undefined"></div>',
+ sortedHtml(doc.node));
+};
+
+BinderTest.prototype.testBindClass = function() {
+ var c = this.compile('<div ng-class="class"/>');
+
+ c.scope.$set('class', 'testClass');
+ c.scope.$eval();
+
+ assertEquals(sortedHtml(c.node),
+ '<div class="testClass" ng-class="class"></div>');
+
+ c.scope.$set('class', ['a', 'b']);
+ c.scope.$eval();
+
+ assertEquals(sortedHtml(c.node),
+ '<div class="a b" ng-class="class"></div>');
+};
+
+BinderTest.prototype.testBindClassEvenOdd = function() {
+ var x = this.compile('<div><div ng-repeat="i in [0,1]" ng-class-even="\'e\'" ng-class-odd="\'o\'"/></div>');
+ x.scope.$eval();
+ assertEquals(
+ '<div><#comment></#comment>' +
+ '<div class="o" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat-index="0"></div>' +
+ '<div class="e" ng-class-even="\'e\'" ng-class-odd="\'o\'" ng-repeat-index="1"></div></div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testBindStyle = function() {
+ var c = this.compile('<div ng-style="style"/>');
+
+ c.scope.$eval('style={color:"red"}');
+ c.scope.$eval();
+
+ assertEquals("red", c.node.css('color'));
+
+ c.scope.$eval('style={}');
+ c.scope.$eval();
+};
+
+BinderTest.prototype.testActionOnAHrefThrowsError = function(){
+ var model = {books:[]};
+ var c = this.compile('<a ng-click="action()">Add Phone</a>', model);
+ c.scope.action = function(){
+ throw {a:'abc', b:2};
+ };
+ var input = c.node;
+ input.trigger('click');
+ var error = fromJson(input.attr('ng-exception'));
+ assertEquals("abc", error.a);
+ assertEquals(2, error.b);
+ assertTrue("should have an error class", input.hasClass('ng-exception'));
+
+ // TODO: I think that exception should never get cleared so this portion of test makes no sense
+ //c.scope.action = noop;
+ //input.trigger('click');
+ //dump(input.attr('ng-error'));
+ //assertFalse('error class should be cleared', input.hasClass('ng-exception'));
+};
+
+BinderTest.prototype.testShoulIgnoreVbNonBindable = function(){
+ var c = this.compile("<div>{{a}}" +
+ "<div ng-non-bindable>{{a}}</div>" +
+ "<div ng-non-bindable=''>{{b}}</div>" +
+ "<div ng-non-bindable='true'>{{c}}</div></div>");
+ c.scope.$set('a', 123);
+ c.scope.$eval();
+ assertEquals('123{{a}}{{b}}{{c}}', c.node.text());
+};
+
+BinderTest.prototype.testOptionShouldUpdateParentToGetProperBinding = function() {
+ var c = this.compile('<select name="s"><option ng-repeat="i in [0,1]" value="{{i}}" ng-bind="i"></option></select>');
+ c.scope.$set('s', 1);
+ c.scope.$eval();
+ assertEquals(1, c.node[0].selectedIndex);
+};
+
+BinderTest.prototype.testRepeaterShouldBindInputsDefaults = function () {
+ var c = this.compile('<div><input value="123" name="item.name" ng-repeat="item in items"></div>');
+ c.scope.$set('items', [{}, {name:'misko'}]);
+ c.scope.$eval();
+
+ assertEquals("123", c.scope.$eval('items[0].name'));
+ assertEquals("misko", c.scope.$eval('items[1].name'));
+};
+
+BinderTest.prototype.testRepeaterShouldCreateArray = function () {
+ var c = this.compile('<input value="123" name="item.name" ng-repeat="item in items">');
+ c.scope.$eval();
+
+ assertEquals(0, c.scope.$get('items').length);
+};
+
+BinderTest.prototype.testShouldTemplateBindPreElements = function () {
+ var c = this.compile('<pre>Hello {{name}}!</pre>');
+ c.scope.$set("name", "World");
+ c.scope.$eval();
+
+ assertEquals('<pre ng-bind-template="Hello {{name}}!">Hello World!</pre>', sortedHtml(c.node));
+};
+
+BinderTest.prototype.testFillInOptionValueWhenMissing = function() {
+ var c = this.compile(
+ '<select><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>');
+ c.scope.$set('a', 'A');
+ c.scope.$set('b', 'B');
+ c.scope.$eval();
+ var optionA = childNode(c.node, 0);
+ var optionB = childNode(c.node, 1);
+ var optionC = childNode(c.node, 2);
+
+ expect(optionA.attr('value')).toEqual('A');
+ expect(optionA.text()).toEqual('A');
+
+ expect(optionB.attr('value')).toEqual('');
+ expect(optionB.text()).toEqual('B');
+
+ expect(optionC.attr('value')).toEqual('C');
+ expect(optionC.text()).toEqual('C');
+};
+
+BinderTest.prototype.testValidateForm = function() {
+ var c = this.compile('<div><input name="name" ng-required>' +
+ '<div ng-repeat="item in items"><input name="item.name" ng-required/></div></div>');
+ var items = [{}, {}];
+ c.scope.$set("items", items);
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', '');
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', ' ');
+ c.scope.$eval();
+ assertEquals(3, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set('name', 'abc');
+ c.scope.$eval();
+ assertEquals(2, c.scope.$get("$invalidWidgets.length"));
+
+ items[0].name = 'abc';
+ c.scope.$eval();
+ assertEquals(1, c.scope.$get("$invalidWidgets.length"));
+
+ items[1].name = 'abc';
+ c.scope.$eval();
+ assertEquals(0, c.scope.$get("$invalidWidgets.length"));
+};
+
+BinderTest.prototype.testValidateOnlyVisibleItems = function(){
+ var c = this.compile('<div><input name="name" ng-required><input ng-show="show" name="name" ng-required></div>');
+ jqLite(document.body).append(c.node);
+ c.scope.$set("show", true);
+ c.scope.$eval();
+ assertEquals(2, c.scope.$get("$invalidWidgets.length"));
+
+ c.scope.$set("show", false);
+ c.scope.$eval();
+ assertEquals(1, c.scope.$invalidWidgets.visible());
+};
+
+BinderTest.prototype.testDeleteAttributeIfEvaluatesFalse = function() {
+ var c = this.compile('<div>' +
+ '<input name="a0" ng-bind-attr="{disabled:\'{{true}}\'}"><input name="a1" ng-bind-attr="{disabled:\'{{false}}\'}">' +
+ '<input name="b0" ng-bind-attr="{disabled:\'{{1}}\'}"><input name="b1" ng-bind-attr="{disabled:\'{{0}}\'}">' +
+ '<input name="c0" ng-bind-attr="{disabled:\'{{[0]}}\'}"><input name="c1" ng-bind-attr="{disabled:\'{{[]}}\'}"></div>');
+ c.scope.$eval();
+ function assertChild(index, disabled) {
+ var child = childNode(c.node, index);
+ assertEquals(sortedHtml(child), disabled, !!child.attr('disabled'));
+ }
+
+ assertChild(0, true);
+ assertChild(1, false);
+ assertChild(2, true);
+ assertChild(3, false);
+ assertChild(4, true);
+ assertChild(5, false);
+};
+
+BinderTest.prototype.testItShouldDisplayErrorWhenActionIsSyntacticlyIncorect = function(){
+ var c = this.compile('<div>' +
+ '<input type="button" ng-click="greeting=\'ABC\'"/>' +
+ '<input type="button" ng-click=":garbage:"/></div>');
+ var first = jqLite(c.node[0].childNodes[0]);
+ var second = jqLite(c.node[0].childNodes[1]);
+
+ first.trigger('click');
+ assertEquals("ABC", c.scope.greeting);
+
+ second.trigger('click');
+ assertTrue(second.hasClass("ng-exception"));
+};
+
+BinderTest.prototype.testItShouldSelectTheCorrectRadioBox = function() {
+ var c = this.compile('<div>' +
+ '<input type="radio" name="sex" value="female"/>' +
+ '<input type="radio" name="sex" value="male"/></div>');
+ var female = jqLite(c.node[0].childNodes[0]);
+ var male = jqLite(c.node[0].childNodes[1]);
+
+ click(female);
+ assertEquals("female", c.scope.sex);
+ assertEquals(true, female[0].checked);
+ assertEquals(false, male[0].checked);
+ assertEquals("female", female.val());
+
+ click(male);
+ assertEquals("male", c.scope.sex);
+ assertEquals(false, female[0].checked);
+ assertEquals(true, male[0].checked);
+ assertEquals("male", male.val());
+};
+
+BinderTest.prototype.testItShouldListenOnRightScope = function() {
+ var c = this.compile(
+ '<ul ng-init="counter=0; gCounter=0" ng-watch="w:counter=counter+1">' +
+ '<li ng-repeat="n in [1,2,4]" ng-watch="w:counter=counter+1;w:$root.gCounter=$root.gCounter+n"/></ul>');
+ c.scope.$eval();
+ assertEquals(0, c.scope.$get("counter"));
+ assertEquals(0, c.scope.$get("gCounter"));
+
+ c.scope.$set("w", "something");
+ c.scope.$eval();
+ assertEquals(1, c.scope.$get("counter"));
+ assertEquals(7, c.scope.$get("gCounter"));
+};
+
+BinderTest.prototype.testItShouldRepeatOnHashes = function() {
+ var x = this.compile('<ul><li ng-repeat="(k,v) in {a:0,b:1}" ng-bind=\"k + v\"></li></ul>');
+ x.scope.$eval();
+ assertEquals('<ul>' +
+ '<#comment></#comment>' +
+ '<li ng-bind=\"k + v\" ng-repeat-index="0">a0</li>' +
+ '<li ng-bind=\"k + v\" ng-repeat-index="1">b1</li>' +
+ '</ul>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldFireChangeListenersBeforeUpdate = function(){
+ var x = this.compile('<div ng-bind="name"></div>');
+ x.scope.$set("name", "");
+ x.scope.$watch("watched", "name=123");
+ x.scope.$set("watched", "change");
+ x.scope.$eval();
+ assertEquals(123, x.scope.$get("name"));
+ assertEquals(
+ '<div ng-bind="name">123</div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldHandleMultilineBindings = function(){
+ var x = this.compile('<div>{{\n 1 \n + \n 2 \n}}</div>');
+ x.scope.$eval();
+ assertEquals("3", x.node.text());
+};
+
+BinderTest.prototype.testItBindHiddenInputFields = function(){
+ var x = this.compile('<input type="hidden" name="myName" value="abc" />');
+ x.scope.$eval();
+ assertEquals("abc", x.scope.$get("myName"));
+};
+
+BinderTest.prototype.XtestItShouldRenderMultiRootHtmlInBinding = function() {
+ var x = this.compile('<div>before {{a|html}}after</div>');
+ x.scope.a = "a<b>c</b>d";
+ x.scope.$eval();
+ assertEquals(
+ '<div>before <span ng-bind="a|html">a<b>c</b>d</span>after</div>',
+ sortedHtml(x.node));
+};
+
+BinderTest.prototype.testItShouldUseFormaterForText = function() {
+ var x = this.compile('<input name="a" ng-format="list" value="a,b">');
+ x.scope.$eval();
+ assertEquals(['a','b'], x.scope.$get('a'));
+ var input = x.node;
+ input[0].value = ' x,,yz';
+ input.trigger('change');
+ assertEquals(['x','yz'], x.scope.$get('a'));
+ x.scope.$set('a', [1 ,2, 3]);
+ x.scope.$eval();
+ assertEquals('1, 2, 3', input[0].value);
+};
+
diff --git a/test/BrowserSpecs.js b/test/BrowserSpecs.js
new file mode 100644
index 00000000..3ce158b4
--- /dev/null
+++ b/test/BrowserSpecs.js
@@ -0,0 +1,48 @@
+describe('browser', function(){
+
+ var browser, location;
+
+ beforeEach(function(){
+ location = {href:"http://server", hash:""};
+ browser = new Browser(location, {});
+ browser.setTimeout = noop;
+ });
+
+ it('should watch url', function(){
+ browser.delay = 1;
+ expectAsserts(2);
+ browser.watchUrl(function(url){
+ assertEquals('http://getangular.test', url);
+ });
+ browser.setTimeout = function(fn, delay){
+ assertEquals(1, delay);
+ location.href = "http://getangular.test";
+ browser.setTimeout = function(fn, delay) {};
+ fn();
+ };
+ browser.startUrlWatcher();
+ });
+
+ describe('outstading requests', function(){
+ it('should process callbacks immedietly with no outstanding requests', function(){
+ var callback = jasmine.createSpy('callback');
+ browser.notifyWhenNoOutstandingRequests(callback);
+ expect(callback).wasCalled();
+ });
+
+ it('should queue callbacks with outstanding requests', function(){
+ var callback = jasmine.createSpy('callback');
+ browser.outstandingRequests.count = 1;
+ browser.notifyWhenNoOutstandingRequests(callback);
+ expect(callback).not.wasCalled();
+
+ browser.processRequestCallbacks();
+ expect(callback).not.wasCalled();
+
+ browser.outstandingRequests.count = 0;
+ browser.processRequestCallbacks();
+ expect(callback).wasCalled();
+ });
+ });
+
+});
diff --git a/test/CompilerSpec.js b/test/CompilerSpec.js
new file mode 100644
index 00000000..da354ea5
--- /dev/null
+++ b/test/CompilerSpec.js
@@ -0,0 +1,137 @@
+describe('compiler', function(){
+ var compiler, textMarkup, directives, widgets, compile, log;
+
+ beforeEach(function(){
+ log = "";
+ directives = {
+ hello: function(expression, element){
+ log += "hello ";
+ return function() {
+ log += expression;
+ };
+ },
+
+ watch: function(expression, element){
+ return function() {
+ this.$watch(expression, function(val){
+ log += ":" + val;
+ });
+ };
+ }
+
+ };
+ textMarkup = [];
+ attrMarkup = [];
+ widgets = {};
+ compiler = new Compiler(textMarkup, attrMarkup, directives, widgets);
+ compile = function(html){
+ var e = jqLite("<div>" + html + "</div>");
+ var scope = compiler.compile(e)(e);
+ scope.$init();
+ return scope;
+ };
+ });
+
+ it('should recognize a directive', function(){
+ var e = jqLite('<div directive="expr" ignore="me"></div>');
+ directives.directive = function(expression, element){
+ log += "found";
+ expect(expression).toEqual("expr");
+ expect(element).toEqual(e);
+ return function initFn() {
+ log += ":init";
+ };
+ };
+ var template = compiler.compile(e);
+ var init = template(e).$init;
+ expect(log).toEqual("found");
+ init();
+ expect(log).toEqual("found:init");
+ });
+
+ it('should recurse to children', function(){
+ var scope = compile('<div><span hello="misko"/></div>');
+ expect(log).toEqual("hello misko");
+ });
+
+ it('should watch scope', function(){
+ var scope = compile('<span watch="name"/>');
+ expect(log).toEqual("");
+ scope.$eval();
+ scope.$set('name', 'misko');
+ scope.$eval();
+ scope.$eval();
+ scope.$set('name', 'adam');
+ scope.$eval();
+ scope.$eval();
+ expect(log).toEqual(":misko:adam");
+ });
+
+ it('should prevent descend', function(){
+ directives.stop = function(){ this.descend(false); };
+ var scope = compile('<span hello="misko" stop="true"><span hello="adam"/></span>');
+ expect(log).toEqual("hello misko");
+ });
+
+ it('should allow creation of templates', function(){
+ directives.duplicate = function(expr, element){
+ var parent = element.parent();
+ element.replaceWith(document.createComment("marker"));
+ element.removeAttr("duplicate");
+ var template = this.compile(element);
+ return function(marker) {
+ this.$onEval(function() {
+ marker.after(template(element.clone()).$element);
+ });
+ };
+ };
+ var scope = compile('before<span duplicate="expr">x</span>after');
+ expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span>after</div>');
+ scope.$eval();
+ expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span>after</div>');
+ scope.$eval();
+ expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment><span>x</span><span>x</span><span>x</span>after</div>');
+ });
+
+ it('should process markup before directives', function(){
+ textMarkup.push(function(text, textNode, parentNode) {
+ if (text == 'middle') {
+ expect(textNode.text()).toEqual(text);
+ parentNode.attr('hello', text);
+ textNode[0].nodeValue = 'replaced';
+ }
+ });
+ var scope = compile('before<span>middle</span>after');
+ expect(lowercase(scope.$element[0].innerHTML)).toEqual('before<span hello="middle">replaced</span>after');
+ expect(log).toEqual("hello middle");
+ });
+
+ it('should replace widgets', function(){
+ widgets['NG:BUTTON'] = function(element) {
+ element.replaceWith('<div>button</div>');
+ return function(element) {
+ log += 'init';
+ };
+ };
+ var scope = compile('<ng:button>push me</ng:button>');
+ expect(lowercase(scope.$element[0].innerHTML)).toEqual('<div>button</div>');
+ expect(log).toEqual('init');
+ });
+
+ it('should use the replaced element after calling widget', function(){
+ widgets['H1'] = function(element) {
+ var span = angular.element('<span>{{1+2}}</span>');
+ element.replaceWith(span);
+ this.descend(true);
+ this.directives(true);
+ return noop;
+ };
+ textMarkup.push(function(text, textNode, parent){
+ if (text == '{{1+2}}')
+ parent.text('3');
+ });
+ var scope = compile('<div><h1>ignore me</h1></div>');
+ expect(scope.$element.text()).toEqual('3');
+ });
+
+});
diff --git a/test/ConsoleTest.js b/test/ConsoleTest.js
new file mode 100644
index 00000000..3e09267b
--- /dev/null
+++ b/test/ConsoleTest.js
@@ -0,0 +1,12 @@
+ConsoleTest = TestCase('ConsoleTest');
+
+ConsoleTest.prototype.XtestConsoleWrite = function(){
+ var consoleNode = jqLite("<div></div>")[0];
+ consoleLog("error", ["Hello", "world"]);
+ assertEquals(jqLite(consoleNode)[0].nodeName, 'DIV');
+ assertEquals(jqLite(consoleNode).text(), 'Hello world');
+ assertEquals(jqLite(consoleNode.childNodes[0])[0].className, 'error');
+ consoleLog("error",["Bye"]);
+ assertEquals(jqLite(consoleNode).text(), 'Hello worldBye');
+ consoleNode = null;
+};
diff --git a/test/FiltersTest.js b/test/FiltersTest.js
new file mode 100644
index 00000000..f839bb51
--- /dev/null
+++ b/test/FiltersTest.js
@@ -0,0 +1,143 @@
+FiltersTest = TestCase('FiltersTest');
+
+FiltersTest.prototype.testCurrency = function(){
+ var html = jqLite('<span/>');
+ var context = {$element:html};
+ var currency = bind(context, angular.filter.currency);
+
+ assertEquals(currency(0), '$0.00');
+ assertEquals(html.hasClass('ng-format-negative'), false);
+ assertEquals(currency(-999), '$-999.00');
+ assertEquals(html.hasClass('ng-format-negative'), true);
+ assertEquals(currency(1234.5678), '$1,234.57');
+ assertEquals(html.hasClass('ng-format-negative'), false);
+};
+
+FiltersTest.prototype.testFilterThisIsContext = function(){
+ expectAsserts(1);
+ var scope = createScope();
+ scope.name = 'misko';
+ angular.filter.testFn = function () {
+ assertEquals('scope not equal', 'misko', this.name);
+ };
+ scope.$eval("0|testFn");
+ delete angular.filter['testFn'];
+};
+
+FiltersTest.prototype.testNumberFormat = function(){
+ var context = {jqElement:jqLite('<span/>')};
+ var number = bind(context, angular.filter.number);
+
+ assertEquals('0', number(0, 0));
+ assertEquals('0.00', number(0));
+ assertEquals('-999.00', number(-999));
+ assertEquals('1,234.57', number(1234.5678));
+ assertEquals('', number(Number.NaN));
+ assertEquals('1,234.57', number("1234.5678"));
+ assertEquals("", number(1/0));
+};
+
+FiltersTest.prototype.testJson = function () {
+ assertEquals(toJson({a:"b"}, true), angular.filter.json.call({$element:jqLite('<div></div>')}, {a:"b"}));
+};
+
+FiltersTest.prototype.testPackageTracking = function () {
+ var assert = function(title, trackingNo) {
+ var val = angular.filter.trackPackage(trackingNo, title);
+ assertNotNull("Did Not Match: " + trackingNo, val);
+ assertEquals(title + ": " + trim(trackingNo), val.text());
+ assertNotNull(val.attr('href'));
+ };
+ assert('UPS', ' 1Z 999 999 99 9999 999 9 ');
+ assert('UPS', '1ZW5w5220379084747');
+
+ assert('FedEx', '418822131061812');
+ assert('FedEx', '9612019 5935 3267 2473 738');
+ assert('FedEx', '9612019593532672473738');
+ assert('FedEx', '235354667129449');
+ assert('FedEx', '915368880571');
+ assert('FedEx', '901712142390');
+ assert('FedEx', '297391510063413');
+
+ assert('USPS', '9101 8052 1390 7402 4335 49');
+ assert('USPS', '9101010521297963339560');
+ assert('USPS', '9102901001301038667029');
+ assert('USPS', '910 27974 4490 3000 8916 56');
+ assert('USPS', '9102801438635051633253');
+};
+
+FiltersTest.prototype.testLink = function() {
+ var assert = function(text, url, obj){
+ var val = angular.filter.link(obj);
+ assertEquals('<a href="' + url + '">' + text + '</a>', sortedHtml(val));
+ };
+ assert("url", "url", "url");
+ assert("hello", "url", {text:"hello", url:"url"});
+ assert("a@b.com", "mailto:a@b.com", "a@b.com");
+};
+
+FiltersTest.prototype.testImage = function(){
+ assertEquals(null, angular.filter.image());
+ assertEquals(null, angular.filter.image({}));
+ assertEquals(null, angular.filter.image(""));
+ assertEquals('http://localhost/abc', angular.filter.image({url:"http://localhost/abc"}).attr('src'));
+};
+
+FiltersTest.prototype.testQRcode = function() {
+ assertEquals(
+ 'http://chart.apis.google.com/chart?chl=Hello%20world&chs=200x200&cht=qr',
+ angular.filter.qrcode('Hello world').attr('src'));
+};
+
+FiltersTest.prototype.testLowercase = function() {
+ assertEquals('abc', angular.filter.lowercase('AbC'));
+ assertEquals(null, angular.filter.lowercase(null));
+};
+
+FiltersTest.prototype.testUppercase = function() {
+ assertEquals('ABC', angular.filter.uppercase('AbC'));
+ assertEquals(null, angular.filter.uppercase(null));
+};
+
+FiltersTest.prototype.testLineCount = function() {
+ assertEquals(1, angular.filter.linecount(null));
+ assertEquals(1, angular.filter.linecount(''));
+ assertEquals(1, angular.filter.linecount('a'));
+ assertEquals(2, angular.filter.linecount('a\nb'));
+ assertEquals(3, angular.filter.linecount('a\nb\nc'));
+};
+
+FiltersTest.prototype.testIf = function() {
+ assertEquals('A', angular.filter['if']('A', true));
+ assertEquals(undefined, angular.filter['if']('A', false));
+};
+
+FiltersTest.prototype.testUnless = function() {
+ assertEquals('A', angular.filter.unless('A', false));
+ assertEquals(undefined, angular.filter.unless('A', true));
+};
+
+FiltersTest.prototype.testGoogleChartApiEncode = function() {
+ assertEquals(
+ 'http://chart.apis.google.com/chart?chl=Hello world&chs=200x200&cht=qr',
+ angular.filter.googleChartApi.encode({cht:"qr", chl:"Hello world"}).attr('src'));
+};
+
+FiltersTest.prototype.testHtml = function() {
+ var html = angular.filter.html("a<b>c</b>d");
+ expect(html instanceof HTML).toBeTruthy();
+ expect(html.html).toEqual("a<b>c</b>d");
+};
+
+FiltersTest.prototype.testLinky = function() {
+ var linky = angular.filter.linky;
+ assertEquals(
+ '<a href="http://ab/">http://ab/</a> ' +
+ '(<a href="http://a/">http://a/</a>) ' +
+ '&lt;<a href="http://a/">http://a/</a>&gt; ' +
+ '<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c',
+ linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c").html);
+ assertEquals(undefined, linky(undefined));
+};
+
+
diff --git a/test/FormattersTest.js b/test/FormattersTest.js
new file mode 100644
index 00000000..b520faf9
--- /dev/null
+++ b/test/FormattersTest.js
@@ -0,0 +1,37 @@
+TestCase("formatterTest", {
+ testNoop: function(){
+ assertEquals("abc", angular.formatter.noop.format("abc"));
+ assertEquals("xyz", angular.formatter.noop.parse("xyz"));
+ assertEquals(null, angular.formatter.noop.parse(null));
+ },
+
+ testList: function() {
+ assertEquals('a, b', angular.formatter.list.format(['a', 'b']));
+ assertEquals('', angular.formatter.list.format([]));
+ assertEquals(['abc', 'c'], angular.formatter.list.parse(" , abc , c ,,"));
+ assertEquals([], angular.formatter.list.parse(""));
+ assertEquals([], angular.formatter.list.parse(null));
+ },
+
+ testBoolean: function() {
+ assertEquals('true', angular.formatter['boolean'].format(true));
+ assertEquals('false', angular.formatter['boolean'].format(false));
+ assertEquals(true, angular.formatter['boolean'].parse("true"));
+ assertEquals(false, angular.formatter['boolean'].parse(""));
+ assertEquals(false, angular.formatter['boolean'].parse("false"));
+ assertEquals(null, angular.formatter['boolean'].parse(null));
+ },
+
+ testNumber: function() {
+ assertEquals('1', angular.formatter.number.format(1));
+ assertEquals(1, angular.formatter.number.format('1'));
+ },
+
+ testTrim: function() {
+ assertEquals('', angular.formatter.trim.format(null));
+ assertEquals('', angular.formatter.trim.format(""));
+ assertEquals('a', angular.formatter.trim.format(" a "));
+ assertEquals('a', angular.formatter.trim.parse(' a '));
+ }
+
+});
diff --git a/test/JsonTest.js b/test/JsonTest.js
new file mode 100644
index 00000000..1ed56da8
--- /dev/null
+++ b/test/JsonTest.js
@@ -0,0 +1,84 @@
+JsonTest = TestCase("JsonTest");
+
+JsonTest.prototype.testPrimitives = function () {
+ assertEquals("null", toJson(0/0));
+ assertEquals("null", toJson(null));
+ assertEquals("true", toJson(true));
+ assertEquals("false", toJson(false));
+ assertEquals("123.45", toJson(123.45));
+ assertEquals('"abc"', toJson("abc"));
+ assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
+};
+
+JsonTest.prototype.testEscaping = function () {
+ assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
+};
+
+JsonTest.prototype.testObjects = function () {
+ assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
+ assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
+ assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
+ assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
+};
+
+JsonTest.prototype.testObjectPretty = function () {
+ assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
+ assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
+};
+
+JsonTest.prototype.testArray = function () {
+ assertEquals('[]', toJson([]));
+ assertEquals('[1,"b"]', toJson([1,"b"]));
+};
+
+JsonTest.prototype.testIgnoreFunctions = function () {
+ assertEquals('[null,1]', toJson([function(){},1]));
+ assertEquals('{}', toJson({a:function(){}}));
+};
+
+JsonTest.prototype.testParseNull = function () {
+ assertNull(fromJson("null"));
+};
+
+JsonTest.prototype.testParseBoolean = function () {
+ assertTrue(fromJson("true"));
+ assertFalse(fromJson("false"));
+};
+
+JsonTest.prototype.test$$isIgnored = function () {
+ assertEquals("{}", toJson({$$:0}));
+};
+
+JsonTest.prototype.testArrayWithEmptyItems = function () {
+ var a = [];
+ a[1] = "X";
+ assertEquals('[null,"X"]', toJson(a));
+};
+
+JsonTest.prototype.testItShouldEscapeUnicode = function () {
+ assertEquals(1, "\u00a0".length);
+ assertEquals(8, toJson("\u00a0").length);
+ assertEquals(1, fromJson(toJson("\u00a0")).length);
+};
+
+JsonTest.prototype.testItShouldUTCDates = function() {
+ var date = angular.String.toDate("2009-10-09T01:02:03Z");
+ assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
+ assertEquals(date.getTime(),
+ fromJson('"2009-10-09T01:02:03Z"').getTime());
+};
+
+JsonTest.prototype.testItShouldPreventRecursion = function () {
+ var obj = {a:'b'};
+ obj.recursion = obj;
+ assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
+};
+
+JsonTest.prototype.testItShouldSerializeSameObjectsMultipleTimes = function () {
+ var obj = {a:'b'};
+ assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
+};
+
+JsonTest.prototype.testItShouldNotSerializeUndefinedValues = function () {
+ assertEquals('{}', angular.toJson({A:undefined}));
+};
diff --git a/test/ParserTest.js b/test/ParserTest.js
new file mode 100644
index 00000000..7ba65f18
--- /dev/null
+++ b/test/ParserTest.js
@@ -0,0 +1,465 @@
+LexerTest = TestCase('LexerTest');
+
+LexerTest.prototype.testTokenizeAString = function(){
+ var lexer = new Lexer("a.bc[22]+1.3|f:'a\\\'c':\"d\\\"e\"");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'a.bc');
+
+ i++;
+ assertEquals(tokens[i].index, 4);
+ assertEquals(tokens[i].text, '[');
+
+ i++;
+ assertEquals(tokens[i].index, 5);
+ assertEquals(tokens[i].text, 22);
+
+ i++;
+ assertEquals(tokens[i].index, 7);
+ assertEquals(tokens[i].text, ']');
+
+ i++;
+ assertEquals(tokens[i].index, 8);
+ assertEquals(tokens[i].text, '+');
+
+ i++;
+ assertEquals(tokens[i].index, 9);
+ assertEquals(tokens[i].text, 1.3);
+
+ i++;
+ assertEquals(tokens[i].index, 12);
+ assertEquals(tokens[i].text, '|');
+
+ i++;
+ assertEquals(tokens[i].index, 13);
+ assertEquals(tokens[i].text, 'f');
+
+ i++;
+ assertEquals(tokens[i].index, 14);
+ assertEquals(tokens[i].text, ':');
+
+ i++;
+ assertEquals(tokens[i].index, 15);
+ assertEquals(tokens[i].string, "a'c");
+
+ i++;
+ assertEquals(tokens[i].index, 21);
+ assertEquals(tokens[i].text, ':');
+
+ i++;
+ assertEquals(tokens[i].index, 22);
+ assertEquals(tokens[i].string, 'd"e');
+};
+
+LexerTest.prototype.testTokenizeUndefined = function(){
+ var lexer = new Lexer("undefined");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'undefined');
+ assertEquals(undefined, tokens[i].fn());
+};
+
+
+
+LexerTest.prototype.testTokenizeRegExp = function(){
+ var lexer = new Lexer("/r 1/");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'r 1');
+ assertEquals("r 1".match(tokens[i].fn())[0], 'r 1');
+};
+
+LexerTest.prototype.testQuotedString = function(){
+ var str = "['\\'', \"\\\"\"]";
+ var lexer = new Lexer(str);
+ var tokens = lexer.parse();
+
+ assertEquals(1, tokens[1].index);
+ assertEquals("'", tokens[1].string);
+
+ assertEquals(7, tokens[3].index);
+ assertEquals('"', tokens[3].string);
+
+};
+
+LexerTest.prototype.testQuotedStringEscape = function(){
+ var str = '"\\"\\n\\f\\r\\t\\v\\u00A0"';
+ var lexer = new Lexer(str);
+ var tokens = lexer.parse();
+
+ assertEquals('"\n\f\r\t\v\u00A0', tokens[0].string);
+};
+
+LexerTest.prototype.testTokenizeUnicode = function(){
+ var lexer = new Lexer('"\\u00A0"');
+ var tokens = lexer.parse();
+ assertEquals(1, tokens.length);
+ assertEquals('\u00a0', tokens[0].string);
+};
+
+LexerTest.prototype.testTokenizeRegExpWithOptions = function(){
+ var lexer = new Lexer("/r/g");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, 'r');
+ assertEquals(tokens[i].flags, 'g');
+ assertEquals("rr".match(tokens[i].fn()).length, 2);
+};
+
+LexerTest.prototype.testTokenizeRegExpWithEscape = function(){
+ var lexer = new Lexer("/\\/\\d/");
+ var tokens = lexer.parse();
+ var i = 0;
+ assertEquals(tokens[i].index, 0);
+ assertEquals(tokens[i].text, '\\/\\d');
+ assertEquals("/1".match(tokens[i].fn())[0], '/1');
+};
+
+LexerTest.prototype.testIgnoreWhitespace = function(){
+ var lexer = new Lexer("a \t \n \r b");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, 'a');
+ assertEquals(tokens[1].text, 'b');
+};
+
+LexerTest.prototype.testRelation = function(){
+ var lexer = new Lexer("! == != < > <= >=");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, '!');
+ assertEquals(tokens[1].text, '==');
+ assertEquals(tokens[2].text, '!=');
+ assertEquals(tokens[3].text, '<');
+ assertEquals(tokens[4].text, '>');
+ assertEquals(tokens[5].text, '<=');
+ assertEquals(tokens[6].text, '>=');
+};
+
+LexerTest.prototype.testStatements = function(){
+ var lexer = new Lexer("a;b;");
+ var tokens = lexer.parse();
+ assertEquals(tokens[0].text, 'a');
+ assertEquals(tokens[1].text, ';');
+ assertEquals(tokens[2].text, 'b');
+ assertEquals(tokens[3].text, ';');
+};
+
+ParserTest = TestCase('ParserTest');
+
+ParserTest.prototype.testExpressions = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("-1"), -1);
+ assertEquals(scope.$eval("1 + 2.5"), 3.5);
+ assertEquals(scope.$eval("1 + -2.5"), -1.5);
+ assertEquals(scope.$eval("1+2*3/4"), 1+2*3/4);
+ assertEquals(scope.$eval("0--1+1.5"), 0- -1 + 1.5);
+ assertEquals(scope.$eval("-0--1++2*-3/-4"), -0- -1+ +2*-3/-4);
+ assertEquals(scope.$eval("1/2*3"), 1/2*3);
+};
+
+ParserTest.prototype.testComparison = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("false"), false);
+ assertEquals(scope.$eval("!true"), false);
+ assertEquals(scope.$eval("1==1"), true);
+ assertEquals(scope.$eval("1!=2"), true);
+ assertEquals(scope.$eval("1<2"), true);
+ assertEquals(scope.$eval("1<=1"), true);
+ assertEquals(scope.$eval("1>2"), 1>2);
+ assertEquals(scope.$eval("2>=1"), 2>=1);
+
+ assertEquals(true === 2<3, scope.$eval("true==2<3"));
+
+};
+
+ParserTest.prototype.testLogical = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("0&&2"), 0&&2);
+ assertEquals(scope.$eval("0||2"), 0||2);
+ assertEquals(scope.$eval("0||1&&2"), 0||1&&2);
+};
+
+ParserTest.prototype.testString = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("'a' + 'b c'"), "ab c");
+};
+
+ParserTest.prototype.testFilters = function(){
+ angular.filter.substring = function(input, start, end) {
+ return input.substring(start, end);
+ };
+
+ angular.filter.upper = {_case:function(input) {
+ return input.toUpperCase();
+ }};
+ var scope = createScope();
+ try {
+ scope.$eval("1|nonExistant");
+ fail();
+ } catch (e) {
+ assertEquals(e, "Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
+ }
+ scope.$set('offset', 3);
+ assertEquals(scope.$eval("'abcd'|upper._case"), "ABCD");
+ assertEquals(scope.$eval("'abcd'|substring:1:offset"), "bc");
+ assertEquals(scope.$eval("'abcd'|substring:1:3|upper._case"), "BC");
+};
+
+ParserTest.prototype.testScopeAccess = function(){
+ var scope = createScope();
+ scope.$set('a', 123);
+ scope.$set('b.c', 456);
+ assertEquals(scope.$eval("a", scope), 123);
+ assertEquals(scope.$eval("b.c", scope), 456);
+ assertEquals(scope.$eval("x.y.z", scope), undefined);
+};
+
+ParserTest.prototype.testGrouping = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("(1+2)*3"), (1+2)*3);
+};
+
+ParserTest.prototype.testAssignments = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("a=12"), 12);
+ assertEquals(scope.$get("a"), 12);
+
+ scope = createScope();
+ assertEquals(scope.$eval("x.y.z=123;"), 123);
+ assertEquals(scope.$get("x.y.z"), 123);
+
+ assertEquals(234, scope.$eval("a=123; b=234"));
+ assertEquals(123, scope.$get("a"));
+ assertEquals(234, scope.$get("b"));
+};
+
+ParserTest.prototype.testFunctionCallsNoArgs = function(){
+ var scope = createScope();
+ scope.$set('const', function(a,b){return 123;});
+ assertEquals(scope.$eval("const()"), 123);
+};
+
+ParserTest.prototype.testFunctionCalls = function(){
+ var scope = createScope();
+ scope.$set('add', function(a,b){
+ return a+b;
+ });
+ assertEquals(3, scope.$eval("add(1,2)"));
+};
+
+ParserTest.prototype.testCalculationBug = function(){
+ var scope = createScope();
+ scope.$set('taxRate', 8);
+ scope.$set('subTotal', 100);
+ assertEquals(scope.$eval("taxRate / 100 * subTotal"), 8);
+ assertEquals(scope.$eval("subTotal * taxRate / 100"), 8);
+};
+
+ParserTest.prototype.testArray = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("[]").length, 0);
+ assertEquals(scope.$eval("[1, 2]").length, 2);
+ assertEquals(scope.$eval("[1, 2]")[0], 1);
+ assertEquals(scope.$eval("[1, 2]")[1], 2);
+};
+
+ParserTest.prototype.testArrayAccess = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("[1][0]"), 1);
+ assertEquals(scope.$eval("[[1]][0][0]"), 1);
+ assertEquals(scope.$eval("[].length"), 0);
+ assertEquals(scope.$eval("[1, 2].length"), 2);
+};
+
+ParserTest.prototype.testObject = function(){
+ var scope = createScope();
+ assertEquals(toJson(scope.$eval("{}")), "{}");
+ assertEquals(toJson(scope.$eval("{a:'b'}")), '{"a":"b"}');
+ assertEquals(toJson(scope.$eval("{'a':'b'}")), '{"a":"b"}');
+ assertEquals(toJson(scope.$eval("{\"a\":'b'}")), '{"a":"b"}');
+};
+
+ParserTest.prototype.testObjectAccess = function(){
+ var scope = createScope();
+ assertEquals("WC", scope.$eval("{false:'WC', true:'CC'}[false]"));
+};
+
+ParserTest.prototype.testJSON = function(){
+ var scope = createScope();
+ assertEquals(toJson(scope.$eval("[{}]")), "[{}]");
+ assertEquals(toJson(scope.$eval("[{a:[]}, {b:1}]")), '[{"a":[]},{"b":1}]');
+};
+
+ParserTest.prototype.testMultippleStatements = function(){
+ var scope = createScope();
+ assertEquals(scope.$eval("a=1;b=3;a+b"), 4);
+ assertEquals(scope.$eval(";;1;;"), 1);
+};
+
+ParserTest.prototype.testParseThrow = function(){
+ expectAsserts(1);
+ var scope = createScope();
+ scope.$set('e', 'abc');
+ try {
+ scope.$eval("throw e");
+ } catch(e) {
+ assertEquals(e, 'abc');
+ }
+};
+
+ParserTest.prototype.testMethodsGetDispatchedWithCorrectThis = function(){
+ var scope = createScope();
+ var C = function (){
+ this.a=123;
+ };
+ C.prototype.getA = function(){
+ return this.a;
+ };
+
+ scope.$set("obj", new C());
+ assertEquals(123, scope.$eval("obj.getA()"));
+};
+ParserTest.prototype.testMethodsArgumentsGetCorrectThis = function(){
+ var scope = createScope();
+ var C = function (){
+ this.a=123;
+ };
+ C.prototype.sum = function(value){
+ return this.a + value;
+ };
+ C.prototype.getA = function(){
+ return this.a;
+ };
+
+ scope.$set("obj", new C());
+ assertEquals(246, scope.$eval("obj.sum(obj.getA())"));
+};
+
+ParserTest.prototype.testObjectPointsToScopeValue = function(){
+ var scope = createScope();
+ scope.$set('a', "abc");
+ assertEquals("abc", scope.$eval("{a:a}").a);
+};
+
+ParserTest.prototype.testFieldAccess = function(){
+ var scope = createScope();
+ var fn = function(){
+ return {name:'misko'};
+ };
+ scope.$set('a', fn);
+ assertEquals("misko", scope.$eval("a().name"));
+};
+
+ParserTest.prototype.testArrayIndexBug = function () {
+ var scope = createScope();
+ scope.$set('items', [{}, {name:'misko'}]);
+
+ assertEquals("misko", scope.$eval('items[1].name'));
+};
+
+ParserTest.prototype.testArrayAssignment = function () {
+ var scope = createScope();
+ scope.$set('items', []);
+
+ assertEquals("abc", scope.$eval('items[1] = "abc"'));
+ assertEquals("abc", scope.$eval('items[1]'));
+// Dont know how to make this work....
+// assertEquals("moby", scope.$eval('books[1] = "moby"'));
+// assertEquals("moby", scope.$eval('books[1]'));
+};
+
+ParserTest.prototype.testFiltersCanBeGrouped = function () {
+ var scope = createScope({name:'MISKO'});
+ assertEquals('misko', scope.$eval('n = (name|lowercase)'));
+ assertEquals('misko', scope.$eval('n'));
+};
+
+ParserTest.prototype.testFiltersCanBeGrouped = function () {
+ var scope = createScope({name:'MISKO'});
+ assertEquals('misko', scope.$eval('n = (name|lowercase)'));
+ assertEquals('misko', scope.$eval('n'));
+};
+
+ParserTest.prototype.testRemainder = function () {
+ var scope = createScope();
+ assertEquals(1, scope.$eval('1%2'));
+};
+
+ParserTest.prototype.testSumOfUndefinedIsNotUndefined = function () {
+ var scope = createScope();
+ assertEquals(1, scope.$eval('1+undefined'));
+ assertEquals(1, scope.$eval('undefined+1'));
+};
+
+ParserTest.prototype.testMissingThrowsError = function() {
+ var scope = createScope();
+ try {
+ scope.$eval('[].count(');
+ fail();
+ } catch (e) {
+ assertEquals('Unexpected end of expression: [].count(', e);
+ }
+};
+
+ParserTest.prototype.testItShouldCreateClosureFunctionWithNoArguments = function () {
+ var scope = createScope();
+ var fn = scope.$eval("{:value}");
+ scope.$set("value", 1);
+ assertEquals(1, fn());
+ scope.$set("value", 2);
+ assertEquals(2, fn());
+ fn = scope.$eval("{():value}");
+ assertEquals(2, fn());
+};
+
+ParserTest.prototype.testItShouldCreateClosureFunctionWithArguments = function () {
+ var scope = createScope();
+ scope.$set("value", 1);
+ var fn = scope.$eval("{(a):value+a}");
+ assertEquals(11, fn(10));
+ scope.$set("value", 2);
+ assertEquals(12, fn(10));
+ fn = scope.$eval("{(a,b):value+a+b}");
+ assertEquals(112, fn(10, 100));
+};
+
+ParserTest.prototype.testItShouldHaveDefaultArugument = function(){
+ var scope = createScope();
+ var fn = scope.$eval("{:$*2}");
+ assertEquals(4, fn(2));
+};
+
+ParserTest.prototype.testDoubleNegationBug = function (){
+ var scope = createScope();
+ assertEquals(true, scope.$eval('true'));
+ assertEquals(false, scope.$eval('!true'));
+ assertEquals(true, scope.$eval('!!true'));
+ assertEquals('a', scope.$eval('{true:"a", false:"b"}[!!true]'));
+};
+
+ParserTest.prototype.testNegationBug = function () {
+ var scope = createScope();
+ assertEquals(!false || true, scope.$eval("!false || true"));
+ assertEquals(!11 == 10, scope.$eval("!11 == 10"));
+ assertEquals(12/6/2, scope.$eval("12/6/2"));
+};
+
+ParserTest.prototype.testBugStringConfusesParser = function() {
+ var scope = createScope();
+ assertEquals('!', scope.$eval('suffix = "!"'));
+};
+
+ParserTest.prototype.testParsingBug = function () {
+ var scope = createScope();
+ assertEquals({a: "-"}, scope.$eval("{a:'-'}"));
+};
+
+ParserTest.prototype.testUndefined = function () {
+ var scope = createScope();
+ assertEquals(undefined, scope.$eval("undefined"));
+ assertEquals(undefined, scope.$eval("a=undefined"));
+ assertEquals(undefined, scope.$get("a"));
+};
diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js
new file mode 100644
index 00000000..d11c3e08
--- /dev/null
+++ b/test/ResourceSpec.js
@@ -0,0 +1,159 @@
+describe("resource", function() {
+ var xhr, resource, CreditCard, callback;
+
+ beforeEach(function(){
+ var browser = new MockBrowser();
+ xhr = browser.xhr;
+ resource = new ResourceFactory(xhr);
+ CreditCard = resource.route('/CreditCard/:id:verb', {id:'@id.key'}, {
+ charge:{
+ method:'POST',
+ params:{verb:'!charge'}
+ }
+ });
+ callback = jasmine.createSpy();
+ });
+
+ it("should build resource", function(){
+ expect(typeof CreditCard).toBe('function');
+ expect(typeof CreditCard.get).toBe('function');
+ expect(typeof CreditCard.save).toBe('function');
+ expect(typeof CreditCard.remove).toBe('function');
+ expect(typeof CreditCard['delete']).toBe('function');
+ expect(typeof CreditCard.query).toBe('function');
+ });
+
+ it('should default to empty parameters', function(){
+ xhr.expectGET('URL').respond({});
+ resource.route('URL').query();
+ });
+
+ it("should build resource with default param", function(){
+ xhr.expectGET('/Order/123/Line/456.visa?minimum=0.05').respond({id:'abc'});
+ xhr.expectGET('/Order/123/Line/456.visa?minimum=0.05').respond({id:'ddd'});
+ var LineItem = resource.route('/Order/:orderId/Line/:id:verb', {orderId: '123', id: '@id.key', verb:'.visa', minimum:0.05});
+ var item = LineItem.get({id:456});
+ xhr.flush();
+ nakedExpect(item).toEqual({id:'abc'});
+
+ item = LineItem.get({id:456});
+ xhr.flush();
+ nakedExpect(item).toEqual({id:'abc'});
+
+ });
+
+ it("should create resource", function(){
+ xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123, name:'misko'});
+
+ var cc = CreditCard.save({name:'misko'}, callback);
+ nakedExpect(cc).toEqual({name:'misko'});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123, name:'misko'});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it("should read resource", function(){
+ xhr.expectGET("/CreditCard/123").respond({id:123, number:'9876'});
+ var cc = CreditCard.get({id:123}, callback);
+ expect(cc instanceof CreditCard).toBeTruthy();
+ nakedExpect(cc).toEqual({});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123, number:'9876'});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it("should update resource", function(){
+ xhr.expectPOST('/CreditCard/123', {id:{key:123}, name:'misko'}).respond({id:{key:123}, name:'rama'});
+
+ var cc = CreditCard.save({id:{key:123}, name:'misko'}, callback);
+ nakedExpect(cc).toEqual({id:{key:123}, name:'misko'});
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ });
+
+ it("should query resource", function(){
+ xhr.expectGET("/CreditCard?key=value").respond([{id:1}, {id:2}]);
+
+ var ccs = CreditCard.query({key:'value'}, callback);
+ expect(ccs).toEqual([]);
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(ccs).toEqual([{id:1}, {id:2}]);
+ expect(callback).wasCalledWith(ccs);
+ });
+
+ it("should have all arguments optional", function(){
+ xhr.expectGET('/CreditCard').respond([{id:1}]);
+ var log = '';
+ var ccs = CreditCard.query(function(){ log += 'cb;'; });
+ xhr.flush();
+ nakedExpect(ccs).toEqual([{id:1}]);
+ expect(log).toEqual('cb;');
+ });
+
+ it('should delete resource', function(){
+ xhr.expectDELETE("/CreditCard/123").respond({});
+
+ CreditCard.remove({id:123}, callback);
+ expect(callback).wasNotCalled();
+ xhr.flush();
+ nakedExpect(callback.mostRecentCall.args).toEqual([{}]);
+ });
+
+ it('should post charge verb', function(){
+ xhr.expectPOST('/CreditCard/123!charge?amount=10', {auth:'abc'}).respond({success:'ok'});
+
+ CreditCard.charge({id:123, amount:10},{auth:'abc'}, callback);
+ });
+
+ it('should create on save', function(){
+ xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123});
+ var cc = new CreditCard();
+ expect(cc.$get).not.toBeDefined();
+ expect(cc.$query).not.toBeDefined();
+ expect(cc.$remove).toBeDefined();
+ expect(cc.$save).toBeDefined();
+
+ cc.name = 'misko';
+ cc.$save(callback);
+ nakedExpect(cc).toEqual({name:'misko'});
+ xhr.flush();
+ nakedExpect(cc).toEqual({id:123});
+ expect(callback).wasCalledWith(cc);
+ });
+
+ it('should bind default parameters', function(){
+ xhr.expectGET('/CreditCard/123.visa?minimum=0.05').respond({id:123});
+ var Visa = CreditCard.bind({verb:'.visa', minimum:0.05});
+ var visa = Visa.get({id:123});
+ xhr.flush();
+ nakedExpect(visa).toEqual({id:123});
+ });
+
+ it('should excersize full stack', function(){
+ var scope = angular.compile('<div></div>');
+ var Person = scope.$resource('/Person/:id');
+ scope.$browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n');
+ var person = Person.get({id:123});
+ scope.$browser.xhr.flush();
+ expect(person.name).toEqual('misko');
+ });
+
+ describe('failure mode', function(){
+ it('should report error when non 200', function(){
+ xhr.expectGET('/CreditCard/123').respond(500, "Server Error");
+ var cc = CreditCard.get({id:123});
+ try {
+ xhr.flush();
+ fail('expected exception, non thrown');
+ } catch (e) {
+ expect(e.status).toEqual(500);
+ expect(e.response).toEqual('Server Error');
+ expect(e.message).toEqual('500: Server Error');
+ }
+ });
+ });
+
+});
diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js
new file mode 100644
index 00000000..9afe8e95
--- /dev/null
+++ b/test/ScenarioSpec.js
@@ -0,0 +1,51 @@
+describe("ScenarioSpec: Compilation", function(){
+ it("should compile dom node and return scope", function(){
+ var node = jqLite('<div ng-init="a=1">{{b=a+1}}</div>')[0];
+ var scope = compile(node);
+ scope.$init();
+ expect(scope.a).toEqual(1);
+ expect(scope.b).toEqual(2);
+ });
+
+ it("should compile jQuery node and return scope", function(){
+ var scope = compile(jqLite('<div>{{a=123}}</div>')).$init();
+ expect(jqLite(scope.$element).text()).toEqual('123');
+ });
+
+ it("should compile text node and return scope", function(){
+ var scope = compile('<div>{{a=123}}</div>').$init();
+ expect(jqLite(scope.$element).text()).toEqual('123');
+ });
+});
+
+describe("ScenarioSpec: Scope", function(){
+ it("should have set, get, eval, $init, updateView methods", function(){
+ var scope = compile('<div>{{a}}</div>').$init();
+ scope.$eval("$invalidWidgets.push({})");
+ expect(scope.$set("a", 2)).toEqual(2);
+ expect(scope.$get("a")).toEqual(2);
+ expect(scope.$eval("a=3")).toEqual(3);
+ scope.$eval();
+ expect(jqLite(scope.$element).text()).toEqual('3');
+ });
+
+ it("should have $ objects", function(){
+ var scope = compile('<div></div>', {$config: {a:"b"}});
+ expect(scope.$get('$location')).toBeDefined();
+ expect(scope.$get('$eval')).toBeDefined();
+ expect(scope.$get('$config')).toBeDefined();
+ expect(scope.$get('$config.a')).toEqual("b");
+ });
+});
+
+describe("ScenarioSpec: configuration", function(){
+ it("should take location object", function(){
+ var url = "http://server/#?book=moby";
+ var scope = compile("<div>{{$location}}</div>");
+ var $location = scope.$get('$location');
+ expect($location.hashSearch.book).toBeUndefined();
+ scope.$browser.setUrl(url);
+ scope.$browser.fireUrlWatchers();
+ expect($location.hashSearch.book).toEqual('moby');
+ });
+});
diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js
new file mode 100644
index 00000000..d93400e5
--- /dev/null
+++ b/test/ScopeSpec.js
@@ -0,0 +1,181 @@
+describe('scope/model', function(){
+
+ it('should create a scope with parent', function(){
+ var model = createScope({name:'Misko'});
+ expect(model.name).toEqual('Misko');
+ });
+
+ it('should have $get/set$/parent$', function(){
+ var parent = {};
+ var model = createScope(parent);
+ model.$set('name', 'adam');
+ expect(model.name).toEqual('adam');
+ expect(model.$get('name')).toEqual('adam');
+ expect(model.$parent).toEqual(model);
+ expect(model.$root).toEqual(model);
+ });
+
+ describe('$eval', function(){
+ it('should eval function with correct this and pass arguments', function(){
+ var model = createScope();
+ model.$eval(function(name){
+ this.name = name;
+ }, 'works');
+ expect(model.name).toEqual('works');
+ });
+
+ it('should eval expression with correct this', function(){
+ var model = createScope();
+ model.$eval('name="works"');
+ expect(model.name).toEqual('works');
+ });
+
+ it('should do nothing on empty string and not update view', function(){
+ var model = createScope();
+ var onEval = jasmine.createSpy('onEval');
+ model.$onEval(onEval);
+ model.$eval('');
+ expect(onEval).wasNotCalled();
+ });
+ });
+
+ describe('$watch', function(){
+ it('should watch an expression for change', function(){
+ var model = createScope();
+ model.oldValue = "";
+ var nameCount = 0, evalCount = 0;
+ model.name = 'adam';
+ model.$watch('name', function(){ nameCount ++; });
+ model.$watch(function(){return model.name;}, function(newValue, oldValue){
+ this.newValue = newValue;
+ this.oldValue = oldValue;
+ });
+ model.$onEval(function(){evalCount ++;});
+ model.name = 'misko';
+ model.$eval();
+ expect(nameCount).toEqual(2);
+ expect(evalCount).toEqual(1);
+ expect(model.newValue).toEqual('misko');
+ expect(model.oldValue).toEqual('adam');
+ });
+
+ it('should eval with no arguments', function(){
+ var model = createScope();
+ var count = 0;
+ model.$onEval(function(){count++;});
+ model.$eval();
+ expect(count).toEqual(1);
+ });
+ });
+
+ describe('$bind', function(){
+ it('should curry a function with respect to scope', function(){
+ var model = createScope();
+ model.name = 'misko';
+ expect(model.$bind(function(){return this.name;})()).toEqual('misko');
+ });
+ });
+
+ describe('$tryEval', function(){
+ it('should report error on element', function(){
+ var scope = createScope();
+ scope.$tryEval('throw "myerror";', function(error){
+ scope.error = error;
+ });
+ expect(scope.error).toEqual('myerror');
+ });
+
+ it('should report error on visible element', function(){
+ var element = jqLite('<div></div>');
+ var scope = createScope();
+ scope.$tryEval('throw "myError"', element);
+ expect(element.attr('ng-exception')).toEqual('"myError"'); // errors are jsonified
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on $excetionHandler', function(){
+ var element = jqLite('<div></div>');
+ var scope = createScope();
+ scope.$exceptionHandler = function(e){
+ this.error = e;
+ };
+ scope.$tryEval('throw "myError"');
+ expect(scope.error).toEqual("myError");
+ });
+ });
+
+ // $onEval
+ describe('$onEval', function(){
+ it("should eval using priority", function(){
+ var scope = createScope();
+ scope.log = "";
+ scope.$onEval('log = log + "middle;"');
+ scope.$onEval(-1, 'log = log + "first;"');
+ scope.$onEval(1, 'log = log + "last;"');
+ scope.$eval();
+ expect(scope.log).toEqual('first;middle;last;');
+ });
+
+ it("should have $root and $parent", function(){
+ var parent = createScope();
+ var scope = createScope(parent);
+ expect(scope.$root).toEqual(parent);
+ expect(scope.$parent).toEqual(parent);
+ });
+ });
+
+ describe('service injection', function(){
+ it('should inject services', function(){
+ var scope = createScope(null, {
+ service:function(){
+ return "ABC";
+ }
+ });
+ expect(scope.service).toEqual("ABC");
+ });
+
+ it('should inject arugments', function(){
+ var scope = createScope(null, {
+ name:function(){
+ return "misko";
+ },
+ greet: extend(function(name) {
+ return 'hello ' + name;
+ }, {inject:['name']})
+ });
+ expect(scope.greet).toEqual("hello misko");
+ });
+
+ it('should throw error on missing dependency', function(){
+ try {
+ createScope(null, {
+ greet: extend(function(name) {
+ }, {inject:['name']})
+ });
+ } catch(e) {
+ expect(e).toEqual("Don't know how to inject 'name'.");
+ }
+ });
+ });
+
+ describe('getterFn', function(){
+ it('should get chain', function(){
+ expect(getterFn('a.b')(undefined)).toEqual(undefined);
+ expect(getterFn('a.b')({})).toEqual(undefined);
+ expect(getterFn('a.b')({a:null})).toEqual(undefined);
+ expect(getterFn('a.b')({a:{}})).toEqual(undefined);
+ expect(getterFn('a.b')({a:{b:null}})).toEqual(null);
+ expect(getterFn('a.b')({a:{b:0}})).toEqual(0);
+ expect(getterFn('a.b')({a:{b:'abc'}})).toEqual('abc');
+ });
+
+ it('should map type method on top of expression', function(){
+ expect(getterFn('a.$filter')({a:[]})('')).toEqual([]);
+ });
+
+ it('should bind function this', function(){
+ expect(getterFn('a')({a:function($){return this.b + $;}, b:1})(2)).toEqual(3);
+
+ });
+ });
+});
diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js
new file mode 100644
index 00000000..573c340d
--- /dev/null
+++ b/test/ValidatorsTest.js
@@ -0,0 +1,169 @@
+ValidatorTest = TestCase('ValidatorTest');
+
+ValidatorTest.prototype.testItShouldHaveThisSet = function() {
+ var validator = {};
+ angular.validator.myValidator = function(first, last){
+ validator.first = first;
+ validator.last = last;
+ validator._this = this;
+ };
+ var scope = compile('<input name="name" ng-validate="myValidator:\'hevery\'"/>');
+ scope.name = 'misko';
+ scope.$init();
+ assertEquals('misko', validator.first);
+ assertEquals('hevery', validator.last);
+ expect(validator._this.$id).toEqual(scope.$id);
+ delete angular.validator.myValidator;
+ scope.$element.remove();
+};
+
+ValidatorTest.prototype.testRegexp = function() {
+ assertEquals(angular.validator.regexp("abc", /x/, "E1"), "E1");
+ assertEquals(angular.validator.regexp("abc", '/x/'),
+ "Value does not match expected format /x/.");
+ assertEquals(angular.validator.regexp("ab", '^ab$'), null);
+ assertEquals(angular.validator.regexp("ab", '^axb$', "E3"), "E3");
+};
+
+ValidatorTest.prototype.testNumber = function() {
+ assertEquals(angular.validator.number("ab"), "Not a number");
+ assertEquals(angular.validator.number("-0.1",0), "Value can not be less than 0.");
+ assertEquals(angular.validator.number("10.1",0,10), "Value can not be greater than 10.");
+ assertEquals(angular.validator.number("1.2"), null);
+ assertEquals(angular.validator.number(" 1 ", 1, 1), null);
+};
+
+ValidatorTest.prototype.testInteger = function() {
+ assertEquals(angular.validator.integer("ab"), "Not a number");
+ assertEquals(angular.validator.integer("1.1"), "Not a whole number");
+ assertEquals(angular.validator.integer("1.0"), "Not a whole number");
+ assertEquals(angular.validator.integer("1."), "Not a whole number");
+ assertEquals(angular.validator.integer("-1",0), "Value can not be less than 0.");
+ assertEquals(angular.validator.integer("11",0,10), "Value can not be greater than 10.");
+ assertEquals(angular.validator.integer("1"), null);
+ assertEquals(angular.validator.integer(" 1 ", 1, 1), null);
+};
+
+ValidatorTest.prototype.testDate = function() {
+ var error = "Value is not a date. (Expecting format: 12/31/2009).";
+ assertEquals(angular.validator.date("ab"), error);
+ assertEquals(angular.validator.date("12/31/2009"), null);
+};
+
+ValidatorTest.prototype.testPhone = function() {
+ var error = "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
+ assertEquals(angular.validator.phone("ab"), error);
+ assertEquals(null, angular.validator.phone("1(408)757-3023"));
+ assertEquals(null, angular.validator.phone("+421 (0905) 933 297"));
+ assertEquals(null, angular.validator.phone("+421 0905 933 297"));
+};
+
+ValidatorTest.prototype.testSSN = function() {
+ var error = "SSN needs to be in 999-99-9999 format.";
+ assertEquals(angular.validator.ssn("ab"), error);
+ assertEquals(angular.validator.ssn("123-45-6789"), null);
+};
+
+ValidatorTest.prototype.testURL = function() {
+ var error = "URL needs to be in http://server[:port]/path format.";
+ assertEquals(angular.validator.url("ab"), error);
+ assertEquals(angular.validator.url("http://server:123/path"), null);
+};
+
+ValidatorTest.prototype.testEmail = function() {
+ var error = "Email needs to be in username@host.com format.";
+ assertEquals(error, angular.validator.email("ab"));
+ assertEquals(null, angular.validator.email("misko@hevery.com"));
+};
+
+ValidatorTest.prototype.testJson = function() {
+ assertNotNull(angular.validator.json("'"));
+ assertNotNull(angular.validator.json("''X"));
+ assertNull(angular.validator.json("{}"));
+};
+
+describe('Validator:asynchronous', function(){
+ var asynchronous = angular.validator.asynchronous;
+ var self;
+ var value, fn;
+
+ beforeEach(function(){
+ var invalidWidgets = angularService('$invalidWidgets')();
+ value = null;
+ fn = null;
+ self = {
+ $element:jqLite('<input />'),
+ $invalidWidgets:invalidWidgets,
+ $eval: noop
+ };
+ self.$element.data('$validate', noop);
+ self.$root = self;
+ });
+
+ afterEach(function(){
+ if (self.$element) self.$element.remove();
+ var oldCache = jqCache;
+ jqCache = {};
+ expect(size(oldCache)).toEqual(0);
+ });
+
+ it('should make a request and show spinner', function(){
+ var value, fn;
+ var scope = compile('<input type="text" name="name" ng-validate="asynchronous:asyncFn"/>');
+ scope.$init();
+ var input = scope.$element;
+ scope.asyncFn = function(v,f){
+ value=v; fn=f;
+ };
+ scope.name = "misko";
+ scope.$eval();
+ expect(value).toEqual('misko');
+ expect(input.hasClass('ng-input-indicator-wait')).toBeTruthy();
+ fn("myError");
+ expect(input.hasClass('ng-input-indicator-wait')).toBeFalsy();
+ expect(input.attr(NG_VALIDATION_ERROR)).toEqual("myError");
+ scope.$element.remove();
+ });
+
+ it("should not make second request to same value", function(){
+ asynchronous.call(self, "kai", function(v,f){value=v; fn=f;});
+ expect(value).toEqual('kai');
+ expect(self.$invalidWidgets[0]).toEqual(self.$element);
+
+ var spy = jasmine.createSpy();
+ asynchronous.call(self, "kai", spy);
+ expect(spy).wasNotCalled();
+
+ asynchronous.call(self, "misko", spy);
+ expect(spy).wasCalled();
+ });
+
+ it("should ignore old callbacks, and not remove spinner", function(){
+ var firstCb, secondCb;
+ asynchronous.call(self, "first", function(v,f){value=v; firstCb=f;});
+ asynchronous.call(self, "second", function(v,f){value=v; secondCb=f;});
+
+ firstCb();
+ expect(self.$element.hasClass('ng-input-indicator-wait')).toBeTruthy();
+
+ secondCb();
+ expect(self.$element.hasClass('ng-input-indicator-wait')).toBeFalsy();
+ });
+
+ it("should handle update function", function(){
+ var scope = angular.compile('<input name="name" ng-validate="asynchronous:asyncFn:updateFn"/>');
+ scope.asyncFn = jasmine.createSpy();
+ scope.updateFn = jasmine.createSpy();
+ scope.name = 'misko';
+ scope.$init();
+ scope.$eval();
+ expect(scope.asyncFn).wasCalledWith('misko', scope.asyncFn.mostRecentCall.args[1]);
+ assertTrue(scope.$element.hasClass('ng-input-indicator-wait'));
+ scope.asyncFn.mostRecentCall.args[1]('myError', {id: 1234, data:'data'});
+ assertFalse(scope.$element.hasClass('ng-input-indicator-wait'));
+ assertEquals('myError', scope.$element.attr('ng-validation-error'));
+ expect(scope.updateFn.mostRecentCall.args[0]).toEqual({id: 1234, data:'data'});
+ scope.$element.remove();
+ });
+
+});
diff --git a/test/angular-mocks.js b/test/angular-mocks.js
new file mode 100644
index 00000000..8838b2cd
--- /dev/null
+++ b/test/angular-mocks.js
@@ -0,0 +1,101 @@
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com
+ *
+ * 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.
+ */
+
+function MockBrowser() {
+ var self = this,
+ expectations = {},
+ requests = [];
+ this.isMock = true;
+ self.url = "http://server";
+ self.watches = [];
+
+ self.xhr = function(method, url, data, callback) {
+ if (angular.isFunction(data)) {
+ callback = data;
+ data = null;
+ }
+ if (data && angular.isObject(data)) data = angular.toJson(data);
+ if (data && angular.isString(data)) url += "|" + data;
+ var expect = expectations[method] || {};
+ var response = expect[url];
+ if (!response) {
+ throw "Unexepected request for method '" + method + "' and url '" + url + "'.";
+ }
+ requests.push(function(){
+ callback(response.code, response.response);
+ });
+ };
+ self.xhr.expectations = expectations;
+ self.xhr.requests = requests;
+ self.xhr.expect = function(method, url, data) {
+ if (data && angular.isObject(data)) data = angular.toJson(data);
+ if (data && angular.isString(data)) url += "|" + data;
+ var expect = expectations[method] || (expectations[method] = {});
+ return {
+ respond: function(code, response) {
+ if (!angular.isNumber(code)) {
+ response = code;
+ code = 200;
+ }
+ expect[url] = {code:code, response:response};
+ }
+ };
+ };
+ self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET');
+ self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST');
+ self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE');
+ self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
+ self.xhr.flush = function() {
+ while(requests.length) {
+ requests.pop()();
+ }
+ };
+}
+MockBrowser.prototype = {
+
+ hover: function(onHover) {
+ },
+
+ getUrl: function(){
+ return this.url;
+ },
+
+ setUrl: function(url){
+ this.url = url;
+ },
+
+ watchUrl: function(fn) {
+ this.watches.push(fn);
+ },
+
+ fireUrlWatchers: function() {
+ for(var i=0; i<this.watches.length; i++) {
+ this.watches[i](this.url);
+ }
+ }
+};
+
+angular.service('$browser', function(){
+ return new MockBrowser();
+});
diff --git a/test/delete/ScopeTest.js b/test/delete/ScopeTest.js
new file mode 100644
index 00000000..24febf19
--- /dev/null
+++ b/test/delete/ScopeTest.js
@@ -0,0 +1,145 @@
+ScopeTest = TestCase('ScopeTest');
+
+ScopeTest.prototype.testGetScopeRetrieval = function(){
+ var scope = {};
+ var form = jQuery("<a><b><c></c></b></a>");
+ form.data('scope', scope);
+ var c = form.find('c');
+ assertTrue(scope === c.scope());
+};
+
+ScopeTest.prototype.testGetScopeRetrievalIntermediateNode = function(){
+ var scope = {};
+ var form = jQuery("<a><b><c></c></b></a>");
+ form.find("b").data('scope', scope);
+ var b = form.find('b');
+ assertTrue(scope === b.scope());
+};
+
+ScopeTest.prototype.testNoScopeDoesNotCauseInfiniteRecursion = function(){
+ var form = jQuery("<a><b><c></c></b></a>");
+ var c = form.find('c');
+ assertTrue(!c.scope());
+};
+
+ScopeTest.prototype.testScopeEval = function(){
+ var scope = new Scope({b:345});
+ assertEquals(scope.eval('b = 123'), 123);
+ assertEquals(scope.get('b'), 123);
+};
+
+ScopeTest.prototype.testScopeFromPrototype = function(){
+ var scope = new Scope({b:123});
+ scope.eval('a = b');
+ scope.eval('b = 456');
+ assertEquals(scope.get('a'), 123);
+ assertEquals(scope.get('b'), 456);
+};
+
+ScopeTest.prototype.testSetScopeGet = function(){
+ var scope = new Scope();
+ assertEquals(987, scope.set('a', 987));
+ assertEquals(scope.get('a'), 987);
+ assertEquals(scope.eval('a'), 987);
+};
+
+ScopeTest.prototype.testGetChain = function(){
+ var scope = new Scope({a:{b:987}});
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testGetUndefinedChain = function(){
+ var scope = new Scope();
+ assertEquals(typeof scope.get('a.b'), 'undefined');
+};
+
+ScopeTest.prototype.testSetChain = function(){
+ var scope = new Scope({a:{}});
+ scope.set('a.b', 987);
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testSetGetOnChain = function(){
+ var scope = new Scope();
+ scope.set('a.b', 987);
+ assertEquals(scope.get('a.b'), 987);
+ assertEquals(scope.eval('a.b'), 987);
+};
+
+ScopeTest.prototype.testGlobalFunctionAccess =function(){
+ window['scopeAddTest'] = function (a, b) {return a+b;};
+ var scope = new Scope({window:window});
+ assertEquals(scope.eval('window.scopeAddTest(1,2)'), 3);
+
+ scope.set('add', function (a, b) {return a+b;});
+ assertEquals(scope.eval('add(1,2)'), 3);
+
+ scope.set('math.add', function (a, b) {return a+b;});
+ assertEquals(scope.eval('math.add(1,2)'), 3);
+};
+
+ScopeTest.prototype.testValidationEval = function(){
+ expectAsserts(4);
+ var scope = new Scope();
+ scope.set("name", "misko");
+ angular.validator.testValidator = function(value, expect){
+ assertEquals("misko", this.name);
+ return value == expect ? null : "Error text";
+ };
+
+ assertEquals("Error text", scope.validate("testValidator:'abc'", 'x'));
+ assertEquals(null, scope.validate("testValidator:'abc'", 'abc'));
+
+ delete angular.validator['testValidator'];
+};
+
+ScopeTest.prototype.testCallingNonExistantMethodShouldProduceFriendlyException = function() {
+ expectAsserts(1);
+ var scope = new Scope({obj:{}});
+ try {
+ scope.eval("obj.iDontExist()");
+ fail();
+ } catch (e) {
+ assertEquals("Expression 'obj.iDontExist' is not a function.", e);
+ }
+};
+
+ScopeTest.prototype.testAccessingWithInvalidPathShouldThrowError = function() {
+ var scope = new Scope();
+ try {
+ scope.get('a.{{b}}');
+ fail();
+ } catch (e) {
+ assertEquals("Expression 'a.{{b}}' is not a valid expression for accesing variables.", e);
+ }
+};
+
+ScopeTest.prototype.testItShouldHave$parent = function() {
+ var parent = new Scope({}, "ROOT");
+ var child = new Scope(parent.state);
+ assertSame("parent", child.state.$parent, parent.state);
+ assertSame("root", child.state.$root, parent.state);
+};
+
+ScopeTest.prototype.testItShouldHave$root = function() {
+ var scope = new Scope({}, "ROOT");
+ assertSame(scope.state.$root, scope.state);
+};
+
+ScopeTest.prototype.testItShouldBuildPathOnUndefined = function(){
+ var scope = new Scope({}, "ROOT");
+ scope.setEval("a.$b.c", 1);
+ assertJsonEquals({$b:{c:1}}, scope.get("a"));
+};
+
+ScopeTest.prototype.testItShouldMapUnderscoreFunctions = function(){
+ var scope = new Scope({}, "ROOT");
+ scope.set("a", [1,2,3]);
+ assertEquals('function', typeof scope.get("a.$size"));
+ scope.eval("a.$includeIf(4,true)");
+ assertEquals(4, scope.get("a.$size")());
+ assertEquals(4, scope.eval("a.$size()"));
+ assertEquals('undefined', typeof scope.get("a.dontExist"));
+};
diff --git a/test/delete/WidgetsTest.js b/test/delete/WidgetsTest.js
new file mode 100644
index 00000000..313d7372
--- /dev/null
+++ b/test/delete/WidgetsTest.js
@@ -0,0 +1,268 @@
+WidgetTest = TestCase('WidgetTest');
+
+WidgetTest.prototype.testRequired = function () {
+ var view = $('<input name="a" ng-required>');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ cntl.updateView(scope);
+ assertTrue(view.hasClass('ng-validation-error'));
+ assertEquals("Required Value", view.attr('ng-error'));
+ scope.set('a', 'A');
+ cntl.updateView(scope);
+ assertFalse(view.hasClass('ng-validation-error'));
+ assertEquals("undefined", typeof view.attr('ng-error'));
+};
+
+WidgetTest.prototype.testValidator = function () {
+ var view = $('<input name="a" ng-validate="testValidator:\'ABC\'">');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ angular.validator.testValidator = function(value, expect){
+ return value == expect ? false : "Error text";
+ };
+
+ scope.set('a', '');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(null, view.attr('ng-error'));
+
+ scope.set('a', 'X');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals(view.attr('ng-error'), "Error text");
+ assertEquals("Error text", view.attr('ng-error'));
+
+ scope.set('a', 'ABC');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(view.attr('ng-error'), null);
+ assertEquals(null, view.attr('ng-error'));
+
+ delete angular.validator['testValidator'];
+};
+
+WidgetTest.prototype.testRequiredValidator = function () {
+ var view = $('<input name="a" ng-required ng-validate="testValidator:\'ABC\'">');
+ var scope = new Scope({$invalidWidgets:[]});
+ var cntl = new TextController(view[0], 'a', angularFormatter.noop);
+ angular.validator.testValidator = function(value, expect){
+ return value == expect ? null : "Error text";
+ };
+
+ scope.set('a', '');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals("Required Value", view.attr('ng-error'));
+
+ scope.set('a', 'X');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), true);
+ assertEquals("Error text", view.attr('ng-error'));
+
+ scope.set('a', 'ABC');
+ cntl.updateView(scope);
+ assertEquals(view.hasClass('ng-validation-error'), false);
+ assertEquals(null, view.attr('ng-error'));
+
+ delete angular.validator['testValidator'];
+};
+
+TextControllerTest = TestCase("TextControllerTest");
+
+TextControllerTest.prototype.testDatePicker = function() {
+ var input = $('<input type="text" ng-widget="datepicker">');
+ input.data('scope', new Scope());
+ var body = $(document.body);
+ body.append(input);
+ var binder = new Binder(input[0], new WidgetFactory());
+ assertTrue('before', input.data('datepicker') === undefined);
+ binder.compile();
+ assertTrue('after', input.data('datepicker') !== null);
+ assertTrue(body.html(), input.hasClass('hasDatepicker'));
+};
+
+RepeaterUpdaterTest = TestCase("RepeaterUpdaterTest");
+
+RepeaterUpdaterTest.prototype.testRemoveThenAdd = function() {
+ var view = $("<div><span/></div>");
+ var template = function () {
+ return $("<li/>");
+ };
+ var repeater = new RepeaterUpdater(view.find("span"), "a in b", template, "");
+ var scope = new Scope();
+ scope.set('b', [1,2]);
+
+ repeater.updateView(scope);
+
+ scope.set('b', []);
+ repeater.updateView(scope);
+
+ scope.set('b', [1]);
+ repeater.updateView(scope);
+ assertEquals(1, view.find("li").size());
+};
+
+RepeaterUpdaterTest.prototype.testShouldBindWidgetOnRepeaterClone = function(){
+ //fail();
+};
+
+RepeaterUpdaterTest.prototype.testShouldThrowInformativeSyntaxError= function(){
+ expectAsserts(1);
+ try {
+ var repeater = new RepeaterUpdater(null, "a=b");
+ } catch (e) {
+ assertEquals("Expected ng-repeat in form of 'item in collection' but got 'a=b'.", e);
+ }
+};
+
+SelectControllerTest = TestCase("SelectControllerTest");
+
+SelectControllerTest.prototype.testShouldUpdateModelNullOnNothingSelected = function(){
+ var scope = new Scope();
+ var view = {selectedIndex:-1, options:[]};
+ var cntl = new SelectController(view, 'abc');
+ cntl.updateModel(scope);
+ assertNull(scope.get('abc'));
+};
+
+SelectControllerTest.prototype.testShouldUpdateModelWhenNothingSelected = function(){
+ var scope = new Scope();
+ var view = {value:'123'};
+ var cntl = new SelectController(view, 'abc');
+ cntl.updateView(scope);
+ assertEquals("123", scope.get('abc'));
+};
+
+BindUpdaterTest = TestCase("BindUpdaterTest");
+
+BindUpdaterTest.prototype.testShouldDisplayNothingForUndefined = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "{{a}}");
+ var scope = new Scope();
+
+ scope.set('a', undefined);
+ controller.updateView(scope);
+ assertEquals("", view.text());
+
+ scope.set('a', null);
+ controller.updateView(scope);
+ assertEquals("", view.text());
+};
+
+BindUpdaterTest.prototype.testShouldDisplayJsonForNonStrings = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+
+ controller.updateView(new Scope({obj:[]}));
+ assertEquals("[]", view.text());
+
+ controller.updateView(new Scope({obj:{text:'abc'}}));
+ assertEquals('abc', fromJson(view.text()).text);
+};
+
+
+BindUpdaterTest.prototype.testShouldInsertHtmlNode = function () {
+ var view = $('<span />');
+ var controller = new BindUpdater(view[0], "<fake>&{{obj}}</fake>");
+ var scope = new Scope();
+
+ scope.set("obj", $('<div>myDiv</div>')[0]);
+ controller.updateView(scope);
+ assertEquals("<fake>&myDiv</fake>", view.text());
+};
+
+
+BindUpdaterTest.prototype.testShouldDisplayTextMethod = function () {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+ var scope = new Scope();
+
+ scope.set("obj", new angular.filter.Meta({text:function(){return "abc";}}));
+ controller.updateView(scope);
+ assertEquals("abc", view.text());
+
+ scope.set("obj", new angular.filter.Meta({text:"123"}));
+ controller.updateView(scope);
+ assertEquals("123", view.text());
+
+ scope.set("obj", {text:"123"});
+ controller.updateView(scope);
+ assertEquals("123", fromJson(view.text()).text);
+};
+
+BindUpdaterTest.prototype.testShouldDisplayHtmlMethod = function () {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{obj}}");
+ var scope = new Scope();
+
+ scope.set("obj", new angular.filter.Meta({html:function(){return "a<div>b</div>c";}}));
+ controller.updateView(scope);
+ assertEquals("abc", view.text());
+
+ scope.set("obj", new angular.filter.Meta({html:"1<div>2</div>3"}));
+ controller.updateView(scope);
+ assertEquals("123", view.text());
+
+ scope.set("obj", {html:"123"});
+ controller.updateView(scope);
+ assertEquals("123", fromJson(view.text()).html);
+};
+
+BindUpdaterTest.prototype.testUdateBoolean = function() {
+ var view = $('<div />');
+ var controller = new BindUpdater(view[0], "{{true}}, {{false}}");
+ controller.updateView(new Scope());
+ assertEquals('true, false', view.text());
+};
+
+BindAttrUpdaterTest = TestCase("BindAttrUpdaterTest");
+
+BindAttrUpdaterTest.prototype.testShouldLoadBlankImageWhenBindingIsUndefined = function () {
+ var view = $('<img />');
+ var controller = new BindAttrUpdater(view[0], {src: '{{imageUrl}}'});
+
+ var scope = new Scope();
+ scope.set('imageUrl', undefined);
+ scope.set('$config.blankImage', 'http://server/blank.gif');
+
+ controller.updateView(scope);
+ assertEquals("http://server/blank.gif", view.attr('src'));
+};
+
+RepeaterUpdaterTest.prototype.testShouldNotDieWhenRepeatExpressionIsNull = function() {
+ var rep = new RepeaterUpdater(null, "$item in items", null, null);
+ var scope = new Scope();
+ scope.set('items', undefined);
+ rep.updateView(scope);
+};
+
+RepeaterUpdaterTest.prototype.testShouldIterateOverKeys = function() {
+ var rep = new RepeaterUpdater(null, "($k,_v) in items", null, null);
+ assertEquals("items", rep.iteratorExp);
+ assertEquals("_v", rep.valueExp);
+ assertEquals("$k", rep.keyExp);
+};
+
+EvalUpdaterTest = TestCase("EvalUpdaterTest");
+EvalUpdaterTest.prototype.testEvalThrowsException = function(){
+ var view = $('<div/>');
+ var eval = new EvalUpdater(view[0], 'undefined()');
+
+ eval.updateView(new Scope());
+ assertTrue(!!view.attr('ng-error'));
+ assertTrue(view.hasClass('ng-exception'));
+
+ eval.exp = "1";
+ eval.updateView(new Scope());
+ assertFalse(!!view.attr('ng-error'));
+ assertFalse(view.hasClass('ng-exception'));
+};
+
+RadioControllerTest = TestCase("RadioController");
+RadioControllerTest.prototype.testItShouldTreatTrueStringAsBoolean = function () {
+ var view = $('<input type="radio" name="select" value="true"/>');
+ var radio = new RadioController(view[0], 'select');
+ var scope = new Scope({select:true});
+ radio.updateView(scope);
+ assertTrue(view[0].checked);
+};
diff --git a/test/directivesSpec.js b/test/directivesSpec.js
new file mode 100644
index 00000000..42869a05
--- /dev/null
+++ b/test/directivesSpec.js
@@ -0,0 +1,226 @@
+describe("directives", function(){
+
+ var compile, model, element;
+
+ beforeEach(function() {
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html) {
+ element = jqLite(html);
+ model = compiler.compile(element)(element);
+ model.$init();
+ return model;
+ };
+ });
+
+ afterEach(function() {
+ if (model && model.$element) model.$element.remove();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ it("should ng-init", function() {
+ var scope = compile('<div ng-init="a=123"></div>');
+ expect(scope.a).toEqual(123);
+ });
+
+ it("should ng-eval", function() {
+ var scope = compile('<div ng-init="a=0" ng-eval="a = a + 1"></div>');
+ expect(scope.a).toEqual(1);
+ scope.$eval();
+ expect(scope.a).toEqual(2);
+ });
+
+ it('should ng-bind', function() {
+ var scope = compile('<div ng-bind="a"></div>');
+ expect(element.text()).toEqual('');
+ scope.a = 'misko';
+ scope.$eval();
+ expect(element.text()).toEqual('misko');
+ });
+
+ it('should ng-bind html', function() {
+ var scope = compile('<div ng-bind="html|html"></div>');
+ scope.html = '<div>hello</div>';
+ scope.$eval();
+ expect(lowercase(element.html())).toEqual('<div>hello</div>');
+ });
+
+ it('should ng-bind element', function() {
+ angularFilter.myElement = function() {
+ return jqLite('<a>hello</a>');
+ };
+ var scope = compile('<div ng-bind="0|myElement"></div>');
+ scope.$eval();
+ expect(lowercase(element.html())).toEqual('<a>hello</a>');
+ });
+
+ it('should ng-bind-template', function() {
+ var scope = compile('<div ng-bind-template="Hello {{name}}!"></div>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(element.text()).toEqual('Hello Misko!');
+ });
+
+ it('should ng-bind-attr', function(){
+ var scope = compile('<img ng-bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>');
+ expect(element.attr('src')).toEqual('http://localhost/mysrc');
+ expect(element.attr('alt')).toEqual('myalt');
+ });
+
+ it('should remove special attributes on false', function(){
+ var scope = compile('<input ng-bind-attr="{disabled:\'{{disabled}}\', readonly:\'{{readonly}}\', checked:\'{{checked}}\'}"/>');
+ var input = scope.$element[0];
+ expect(input.disabled).toEqual(false);
+ expect(input.readOnly).toEqual(false);
+ expect(input.checked).toEqual(false);
+
+ scope.disabled = true;
+ scope.readonly = true;
+ scope.checked = true;
+ scope.$eval();
+
+ expect(input.disabled).toEqual(true);
+ expect(input.readOnly).toEqual(true);
+ expect(input.checked).toEqual(true);
+ });
+
+ it('should ng-non-bindable', function(){
+ var scope = compile('<div ng-non-bindable><span ng-bind="name"></span></div>');
+ scope.$set('name', 'misko');
+ scope.$eval();
+ expect(element.text()).toEqual('');
+ });
+
+ it('should ng-repeat over array', function(){
+ var scope = compile('<ul><li ng-repeat="item in items" ng-init="suffix = \';\'" ng-bind="item + suffix"></li></ul>');
+
+ scope.$set('items', ['misko', 'shyam']);
+ scope.$eval();
+ expect(element.text()).toEqual('misko;shyam;');
+
+ scope.$set('items', ['adam', 'kai', 'brad']);
+ scope.$eval();
+ expect(element.text()).toEqual('adam;kai;brad;');
+
+ scope.$set('items', ['brad']);
+ scope.$eval();
+ expect(element.text()).toEqual('brad;');
+ });
+
+ it('should ng-repeat over object', function(){
+ var scope = compile('<ul><li ng-repeat="(key, value) in items" ng-bind="key + \':\' + value + \';\' "></li></ul>');
+ scope.$set('items', {misko:'swe', shyam:'set'});
+ scope.$eval();
+ expect(element.text()).toEqual('misko:swe;shyam:set;');
+ });
+
+ it('should set ng-repeat to [] if undefinde', function(){
+ var scope = compile('<ul><li ng-repeat="item in items"></li></ul>');
+ expect(scope.items).toEqual([]);
+ });
+
+ it('should error on wrong parsing of ng-repeat', function(){
+ var scope = compile('<ul><li ng-repeat="i dont parse"></li></ul>');
+ var log = "";
+ log += element.attr('ng-exception') + ';';
+ log += element.hasClass('ng-exception') + ';';
+ expect(log).toEqual("\"Expected ng-repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
+ });
+
+ it('should ng-watch', function(){
+ var scope = compile('<div ng-watch="i: count = count + 1" ng-init="count = 0">');
+ scope.$eval();
+ scope.$eval();
+ expect(scope.$get('count')).toEqual(0);
+
+ scope.$set('i', 0);
+ scope.$eval();
+ scope.$eval();
+ expect(scope.$get('count')).toEqual(1);
+ });
+
+ it('should ng-click', function(){
+ var scope = compile('<div ng-click="clicked = true"></div>');
+ scope.$eval();
+ expect(scope.$get('clicked')).toBeFalsy();
+
+ element.trigger('click');
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ it('should ng-class', function(){
+ var scope = compile('<div class="existing" ng-class="[\'A\', \'B\']"></div>');
+ scope.$eval();
+ expect(element.hasClass('existing')).toBeTruthy();
+ expect(element.hasClass('A')).toBeTruthy();
+ expect(element.hasClass('B')).toBeTruthy();
+ });
+
+ it('should ng-class odd/even', function(){
+ var scope = compile('<ul><li ng-repeat="i in [0,1]" class="existing" ng-class-odd="\'odd\'" ng-class-even="\'even\'"></li><ul>');
+ scope.$eval();
+ var e1 = jqLite(element[0].childNodes[1]);
+ var e2 = jqLite(element[0].childNodes[2]);
+ expect(e1.hasClass('existing')).toBeTruthy();
+ expect(e1.hasClass('odd')).toBeTruthy();
+ expect(e2.hasClass('existing')).toBeTruthy();
+ expect(e2.hasClass('even')).toBeTruthy();
+ });
+
+ it('should ng-style', function(){
+ var scope = compile('<div ng-style="{color:\'red\'}"></div>');
+ scope.$eval();
+ expect(element.css('color')).toEqual('red');
+ });
+
+ it('should ng-show', function(){
+ var scope = compile('<div ng-hide="hide"></div>');
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(true);
+ scope.$set('hide', true);
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(false);
+ });
+
+ it('should ng-hide', function(){
+ var scope = compile('<div ng-show="show"></div>');
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(false);
+ scope.$set('show', true);
+ scope.$eval();
+ expect(isCssVisible(scope.$element)).toEqual(true);
+ });
+
+ describe('ng-controller', function(){
+ it('should bind', function(){
+ window.Greeter = function(){
+ this.greeting = 'hello';
+ };
+ window.Greeter.prototype = {
+ init: function(){
+ this.suffix = '!';
+ },
+ greet: function(name) {
+ return this.greeting + ' ' + name + this.suffix;
+ }
+ };
+ var scope = compile('<div ng-controller="Greeter"></div>');
+ expect(scope.greeting).toEqual('hello');
+ expect(scope.greet('misko')).toEqual('hello misko!');
+ window.Greeter = undefined;
+ });
+ });
+
+ it('should eval things according to ng-eval-order', function(){
+ var scope = compile(
+ '<div ng-init="log=\'\'">' +
+ '{{log = log + \'e\'}}' +
+ '<span ng-eval-order="first" ng-eval="log = log + \'a\'">' +
+ '{{log = log + \'b\'}}' +
+ '<span src="{{log = log + \'c\'}}"></span>' +
+ '<span bind-template="{{log = log + \'d\'}}"></span>' +
+ '</span>' +
+ '</div>');
+ expect(scope.log).toEqual('abcde');
+ });
+
+});
diff --git a/test/jquery_alias.js b/test/jquery_alias.js
new file mode 100644
index 00000000..4b3fad00
--- /dev/null
+++ b/test/jquery_alias.js
@@ -0,0 +1 @@
+var _jQuery = jQuery; \ No newline at end of file
diff --git a/test/jquery_remove.js b/test/jquery_remove.js
new file mode 100644
index 00000000..5283c340
--- /dev/null
+++ b/test/jquery_remove.js
@@ -0,0 +1 @@
+var _jQuery = jQuery.noConflict(true); \ No newline at end of file
diff --git a/test/markupSpec.js b/test/markupSpec.js
new file mode 100644
index 00000000..8358b673
--- /dev/null
+++ b/test/markupSpec.js
@@ -0,0 +1,138 @@
+describe("markups", function(){
+
+ var compile, element, scope;
+
+ beforeEach(function() {
+ scope = null;
+ element = null;
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html) {
+ element = jqLite(html);
+ scope = compiler.compile(element)(element);
+ scope.$init();
+ };
+ });
+
+ afterEach(function(){
+ if (element) element.remove();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ it('should translate {{}} in text', function(){
+ compile('<div>hello {{name}}!</div>');
+ expect(sortedHtml(element)).toEqual('<div>hello <span ng-bind="name"></span>!</div>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(sortedHtml(element)).toEqual('<div>hello <span ng-bind="name">Misko</span>!</div>');
+ });
+
+ it('should translate {{}} in terminal nodes', function(){
+ compile('<select name="x"><option value="">Greet {{name}}!</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng-bind-template="Greet {{name}}!">Greet !</option></select>');
+ scope.$set('name', 'Misko');
+ scope.$eval();
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option ng-bind-template="Greet {{name}}!">Greet Misko!</option></select>');
+ });
+
+ it('should translate {{}} in attributes', function(){
+ compile('<img src="http://server/{{path}}.png"/>');
+ expect(element.attr('ng-bind-attr')).toEqual('{"src":"http://server/{{path}}.png"}');
+ scope.$set('path', 'a/b');
+ scope.$eval();
+ expect(element.attr('src')).toEqual("http://server/a/b.png");
+ });
+
+ it('should populate value attribute on OPTION', function(){
+ compile('<select name="x"><option>a</option></select>');
+ expect(sortedHtml(element).replace(' selected="true"', '')).toEqual('<select name="x"><option value="a">a</option></select>');
+ });
+
+});
+
+
+var BindingMarkupTest = TestCase("BindingMarkupTest");
+
+BindingMarkupTest.prototype.testParseTextWithNoBindings = function(){
+ var parts = parseBindings("a");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+};
+
+BindingMarkupTest.prototype.testParseEmptyText = function(){
+ var parts = parseBindings("");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "");
+ assertTrue(!binding(parts[0]));
+};
+
+BindingMarkupTest.prototype.testParseInnerBinding = function(){
+ var parts = parseBindings("a{{b}}c");
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+ assertEquals(parts[1], "{{b}}");
+ assertEquals(binding(parts[1]), "b");
+ assertEquals(parts[2], "c");
+ assertTrue(!binding(parts[2]));
+};
+
+BindingMarkupTest.prototype.testParseEndingBinding = function(){
+ var parts = parseBindings("a{{b}}");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "a");
+ assertTrue(!binding(parts[0]));
+ assertEquals(parts[1], "{{b}}");
+ assertEquals(binding(parts[1]), "b");
+};
+
+BindingMarkupTest.prototype.testParseBeggingBinding = function(){
+ var parts = parseBindings("{{b}}c");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "c");
+ assertTrue(!binding(parts[1]));
+};
+
+BindingMarkupTest.prototype.testParseLoanBinding = function(){
+ var parts = parseBindings("{{b}}");
+ assertEquals(parts.length, 1);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+};
+
+BindingMarkupTest.prototype.testParseTwoBindings = function(){
+ var parts = parseBindings("{{b}}{{c}}");
+ assertEquals(parts.length, 2);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "{{c}}");
+ assertEquals(binding(parts[1]), "c");
+};
+
+BindingMarkupTest.prototype.testParseTwoBindingsWithTextInMiddle = function(){
+ var parts = parseBindings("{{b}}x{{c}}");
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], "{{b}}");
+ assertEquals(binding(parts[0]), "b");
+ assertEquals(parts[1], "x");
+ assertTrue(!binding(parts[1]));
+ assertEquals(parts[2], "{{c}}");
+ assertEquals(binding(parts[2]), "c");
+};
+
+BindingMarkupTest.prototype.testParseMultiline = function(){
+ var parts = parseBindings('"X\nY{{A\nB}}C\nD"');
+ assertTrue(!!binding('{{A\nB}}'));
+ assertEquals(parts.length, 3);
+ assertEquals(parts[0], '"X\nY');
+ assertEquals(parts[1], '{{A\nB}}');
+ assertEquals(parts[2], 'C\nD"');
+};
+
+BindingMarkupTest.prototype.testHasBinding = function(){
+ assertTrue(hasBindings(parseBindings("{{a}}")));
+ assertTrue(!hasBindings(parseBindings("a")));
+ assertTrue(hasBindings(parseBindings("{{b}}x{{c}}")));
+};
diff --git a/test/moveToAngularCom/Base64Test.js b/test/moveToAngularCom/Base64Test.js
new file mode 100644
index 00000000..a9353186
--- /dev/null
+++ b/test/moveToAngularCom/Base64Test.js
@@ -0,0 +1,5 @@
+Base64Test = TestCase('Base64Test');
+
+Base64Test.prototype.testEncodeDecode = function(){
+ assertEquals(Base64.decode(Base64.encode('hello')), 'hello');
+};
diff --git a/test/moveToAngularCom/DataStoreTest.js b/test/moveToAngularCom/DataStoreTest.js
new file mode 100644
index 00000000..87c5be2e
--- /dev/null
+++ b/test/moveToAngularCom/DataStoreTest.js
@@ -0,0 +1,616 @@
+DataStoreTest = TestCase('DataStoreTest');
+
+DataStoreTest.prototype.testSavePostsToServer = function(){
+ expectAsserts(10);
+ var response;
+ var post = function(data, callback){
+ var method = data[0][0];
+ var posted = data[0][2];
+ assertEquals("POST", method);
+ assertEquals("abc", posted.$entity);
+ assertEquals("123", posted.$id);
+ assertEquals("1", posted.$version);
+ assertFalse('function' == typeof posted.save);
+ response = fromJson(toJson(posted));
+ response.$entity = "abc";
+ response.$id = "123";
+ response.$version = "2";
+ callback(200, [response]);
+ };
+ var datastore = new DataStore(post);
+ var model = datastore.entity('abc', {name: "value"})();
+ model.$id = "123";
+ model.$version = "1";
+
+ datastore.save(model, function(obj){
+ assertTrue(obj === model);
+ assertEquals(obj.$entity, "abc");
+ assertEquals(obj.$id, "123");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.name, "value");
+ obj.after = true;
+ });
+ datastore.flush();
+};
+
+DataStoreTest.prototype.testLoadGetsFromServer = function(){
+ expectAsserts(12);
+ var post = function(data, callback){
+ var method = data[0][0];
+ var path = data[0][1];
+ assertEquals("GET", method);
+ assertEquals("abc/1", path);
+ response = [{$entity:'abc', $id:'1', $version:'2', key:"value"}];
+ callback(200, response);
+ };
+ var datastore = new DataStore(post);
+
+ var model = datastore.entity("abc", {merge:true})();
+ assertEquals(datastore.load(model, '1', function(obj){
+ assertEquals(obj.$entity, "abc");
+ assertEquals(obj.$id, "1");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.key, "value");
+ }), model);
+ datastore.flush();
+ assertEquals(model.$entity, "abc");
+ assertEquals(model.$id, "1");
+ assertEquals(model.$version, "2");
+ assertEquals(model.key, "value");
+ assertEquals(model.merge, true);
+};
+
+DataStoreTest.prototype.testRemove = function(){
+ expectAsserts(8);
+ var response;
+ var post = function(data, callback){
+ var method = data[0][0];
+ var posted = data[0][2];
+ assertEquals("DELETE", method);
+ assertEquals("abc", posted.$entity);
+ assertEquals("123", posted.$id);
+ assertEquals("1", posted.$version);
+ assertFalse('function' == typeof posted.save);
+ response = fromJson(toJson(posted));
+ response.$entity = "abc";
+ response.$id = "123";
+ response.$version = "2";
+ callback(200, [response]);
+ };
+ var model;
+ var datastore = new DataStore(post);
+ model = datastore.entity('abc', {name: "value"})();
+ model.$id = "123";
+ model.$version = "1";
+
+ datastore.remove(model, function(obj){
+ assertEquals(obj.$id, "123");
+ assertEquals(obj.$version, "2");
+ assertEquals(obj.name, "value");
+ obj.after = true;
+ });
+ datastore.flush();
+
+};
+
+
+DataStoreTest.prototype.test401ResponseDoesNotCallCallback = function(){
+ expectAsserts(1);
+ var post = function(data, callback) {
+ callback(200, {$status_code: 401});
+ };
+
+ var datastore = new DataStore(post, {login:function(){
+ assertTrue(true);
+ }});
+
+ var onLoadAll = function(){
+ assertTrue(false, "onLoadAll should not be called when response is status 401");
+ };
+ datastore.bulkRequest.push({});
+ datastore.flush();
+ datastore.loadAll({type: "A"}, onLoadAll);
+};
+
+DataStoreTest.prototype.test403ResponseDoesNotCallCallback = function(){
+ expectAsserts(1);
+ var post = function(data, callback) {
+ callback(200, [{$status_code: 403}]);
+ };
+
+ var datastore = new DataStore(post, {notAuthorized:function(){
+ assertTrue(true);
+ }});
+
+ var onLoadAll = function(){
+ assertTrue(false, "onLoadAll should not be called when response is status 403");
+ };
+ datastore.bulkRequest.push({});
+ datastore.flush();
+ datastore.loadAll({type: "A"}, onLoadAll);
+};
+
+DataStoreTest.prototype.testLoadCalledWithoutIdShouldBeNoop = function(){
+ expectAsserts(2);
+ var post = function(url, callback){
+ assertTrue(false);
+ };
+ var datastore = new DataStore(post);
+ var model = datastore.entity("abc")();
+ assertEquals(datastore.load(model, undefined), model);
+ assertEquals(model.$entity, "abc");
+};
+
+DataStoreTest.prototype.testEntityFactory = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe", {a:1, b:2});
+ assertEquals(Recipe.title, "Recipe");
+ assertEquals(Recipe.defaults.a, 1);
+ assertEquals(Recipe.defaults.b, 2);
+
+ var recipe = Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+ assertEquals(recipe.a, 1);
+ assertEquals(recipe.b, 2);
+
+ recipe = new Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+ assertEquals(recipe.a, 1);
+ assertEquals(recipe.b, 2);
+};
+
+DataStoreTest.prototype.testEntityFactoryNoDefaults = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+ assertEquals(Recipe.title, "Recipe");
+
+ recipe = new Recipe();
+ assertEquals(recipe.$entity, "Recipe");
+};
+
+DataStoreTest.prototype.testEntityFactoryWithInitialValues = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+
+ var recipe = Recipe({name: "name"});
+ assertEquals("name", recipe.name);
+};
+
+DataStoreTest.prototype.testEntityLoad = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe", {a:1, b:2});
+ ds.load = function(instance, id, callback){
+ callback.apply(instance);
+ return instance;
+ };
+ var instance = null;
+ var recipe2 = Recipe.load("ID", function(){
+ instance = this;
+ });
+ assertTrue(recipe2 === instance);
+};
+
+DataStoreTest.prototype.testSaveScope = function(){
+ var ds = new DataStore();
+ var log = "";
+ var Person = ds.entity("Person");
+ var person1 = Person({name:"A", $entity:"Person", $id:"1", $version:"1"}, ds);
+ person1.$$anchor = "A";
+ var person2 = Person({name:"B", $entity:"Person", $id:"2", $version:"2"}, ds);
+ person2.$$anchor = "B";
+ var anchor = {};
+ ds.anchor = anchor;
+ ds._jsonRequest = function(request, callback){
+ log += "save(" + request[2].$id + ");";
+ callback({$id:request[2].$id});
+ };
+ ds.saveScope({person1:person1, person2:person2,
+ ignoreMe:{name: "ignore", save:function(callback){callback();}}}, function(){
+ log += "done();";
+ });
+ assertEquals("save(1);save(2);done();", log);
+ assertEquals(1, anchor.A);
+ assertEquals(2, anchor.B);
+};
+
+DataStoreTest.prototype.testEntityLoadAllRows = function(){
+ var ds = new DataStore();
+ var Recipe = ds.entity("Recipe");
+ var list = [];
+ ds.loadAll = function(entity, callback){
+ assertTrue(Recipe === entity);
+ callback.apply(list);
+ return list;
+ };
+ var items = Recipe.all(function(){
+ assertTrue(list === this);
+ });
+ assertTrue(items === list);
+};
+
+DataStoreTest.prototype.testLoadAll = function(){
+ expectAsserts(8);
+ var post = function(data, callback){
+ assertEquals("GET", data[0][0]);
+ assertEquals("A", data[0][1]);
+ callback(200, [[{$entity:'A', $id:'1'},{$entity:'A', $id:'2'}]]);
+ };
+ var datastore = new DataStore(post);
+ var list = datastore.entity("A").all(function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertEquals(list.length, 2);
+ assertEquals(list[0].$entity, "A");
+ assertEquals(list[0].$id, "1");
+ assertEquals(list[1].$entity, "A");
+ assertEquals(list[1].$id, "2");
+};
+
+DataStoreTest.prototype.testQuery = function(){
+ expectAsserts(5);
+ var post = function(data, callback) {
+ assertEquals("GET", data[0][0]);
+ assertEquals("Employee/managerId=123abc", data[0][1]);
+ callback(200, [[{$entity:"Employee", $id: "456", managerId: "123ABC"}]]);
+
+ };
+ var datastore = new DataStore(post);
+ var Employee = datastore.entity("Employee");
+ var list = Employee.query('managerId', "123abc", function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertJsonEquals([[{$entity:"Employee", $id: "456", managerId: "123ABC"}]], datastore._cache.$collections);
+ assertEquals(list[0].$id, "456");
+};
+
+DataStoreTest.prototype.testLoadingDocumentRefreshesExistingArrays = function() {
+ expectAsserts(12);
+ var post;
+ var datastore = new DataStore(function(r, c){post(r,c);});
+ var Book = datastore.entity('Book');
+ post = function(req, callback) {
+ callback(200, [[{$id:1, $entity:"Book", name:"Moby"},
+ {$id:2, $entity:"Book", name:"Dick"}]]);
+ };
+ var allBooks = Book.all();
+ datastore.flush();
+ var queryBooks = Book.query("a", "b");
+ datastore.flush();
+ assertEquals("Moby", allBooks[0].name);
+ assertEquals("Dick", allBooks[1].name);
+ assertEquals("Moby", queryBooks[0].name);
+ assertEquals("Dick", queryBooks[1].name);
+
+ post = function(req, callback) {
+ assertEquals('[["GET","Book/1"]]', toJson(req));
+ callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]);
+ };
+ var book = Book.load(1);
+ datastore.flush();
+ assertEquals("Moby Dick", book.name);
+ assertEquals("Moby Dick", allBooks[0].name);
+ assertEquals("Moby Dick", queryBooks[0].name);
+
+ post = function(req, callback) {
+ assertEquals('POST', req[0][0]);
+ callback(200, [{$id:1, $entity:"Book", name:"The Big Fish"}]);
+ };
+ book.$save();
+ datastore.flush();
+ assertEquals("The Big Fish", book.name);
+ assertEquals("The Big Fish", allBooks[0].name);
+ assertEquals("The Big Fish", queryBooks[0].name);
+};
+
+DataStoreTest.prototype.testEntityProperties = function() {
+ expectAsserts(2);
+ var datastore = new DataStore();
+ var callback = {};
+
+ datastore._jsonRequest = function(request, callbackFn) {
+ assertJsonEquals(["GET", "Cheese/$properties"], request);
+ assertEquals(callback, callbackFn);
+ };
+
+ var Cheese = datastore.entity("Cheese");
+ Cheese.properties(callback);
+
+};
+
+DataStoreTest.prototype.testLoadInstanceIsNotFromCache = function() {
+ var post;
+ var datastore = new DataStore(function(r, c){post(r,c);});
+ var Book = datastore.entity('Book');
+
+ post = function(req, callback) {
+ assertEquals('[["GET","Book/1"]]', toJson(req));
+ callback(200, [{$id:1, $entity:"Book", name:"Moby Dick"}]);
+ };
+ var book = Book.load(1);
+ datastore.flush();
+ assertEquals("Moby Dick", book.name);
+ assertFalse(book === datastore._cache['Book/1']);
+};
+
+DataStoreTest.prototype.testLoadStarsIsNewDocument = function() {
+ var datastore = new DataStore();
+ var Book = datastore.entity('Book');
+ var book = Book.load('*');
+ assertEquals('Book', book.$entity);
+};
+
+DataStoreTest.prototype.testUndefinedEntityReturnsNullValueObject = function() {
+ var datastore = new DataStore();
+ var Entity = datastore.entity(undefined);
+ var all = Entity.all();
+ assertEquals(0, all.length);
+};
+
+DataStoreTest.prototype.testFetchEntities = function(){
+ expectAsserts(6);
+ var post = function(data, callback){
+ assertJsonEquals(["GET", "$entities"], data[0]);
+ callback(200, [{A:0, B:0}]);
+ };
+ var datastore = new DataStore(post);
+ var entities = datastore.entities(function(){
+ assertTrue(true);
+ });
+ datastore.flush();
+ assertJsonEquals([], datastore.bulkRequest);
+ assertEquals(2, entities.length);
+ assertEquals("A", entities[0].title);
+ assertEquals("B", entities[1].title);
+};
+
+DataStoreTest.prototype.testItShouldMigrateSchema = function() {
+ var datastore = new DataStore();
+ var Entity = datastore.entity("Entity", {a:[], user:{name:"Misko", email:""}});
+ var doc = Entity().$loadFrom({b:'abc', user:{email:"misko@hevery.com"}});
+ assertFalse(
+ toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}) ==
+ toJson(doc));
+ doc.$migrate();
+ assertEquals(
+ toJson({a:[], b:'abc', user:{name:"Misko", email:"misko@hevery.com"}}),
+ toJson(doc));
+};
+
+DataStoreTest.prototype.testItShouldCollectRequestsForBulk = function() {
+ var ds = new DataStore();
+ var Book = ds.entity("Book");
+ var Library = ds.entity("Library");
+ Book.all();
+ Library.load("123");
+ assertEquals(2, ds.bulkRequest.length);
+ assertJsonEquals(["GET", "Book"], ds.bulkRequest[0]);
+ assertJsonEquals(["GET", "Library/123"], ds.bulkRequest[1]);
+};
+
+DataStoreTest.prototype.testEmptyFlushShouldDoNothing = function () {
+ var ds = new DataStore(function(){
+ fail("expecting noop");
+ });
+ ds.flush();
+};
+
+DataStoreTest.prototype.testFlushShouldCallAllCallbacks = function() {
+ var log = "";
+ function post(request, callback){
+ log += 'BulkRequest:' + toJson(request) + ';';
+ callback(200, [[{$id:'ABC'}], {$id:'XYZ'}]);
+ }
+ var ds = new DataStore(post);
+ var Book = ds.entity("Book");
+ var Library = ds.entity("Library");
+ Book.all(function(instance){
+ log += toJson(instance) + ';';
+ });
+ Library.load("123", function(instance){
+ log += toJson(instance) + ';';
+ });
+ assertEquals("", log);
+ ds.flush();
+ assertJsonEquals([], ds.bulkRequest);
+ assertEquals('BulkRequest:[["GET","Book"],["GET","Library/123"]];[{"$id":"ABC"}];{"$id":"XYZ"};', log);
+};
+
+DataStoreTest.prototype.testSaveOnNotLoggedInRetriesAfterLoggin = function(){
+ var log = "";
+ var book;
+ var ds = new DataStore(null, {login:function(c){c();}});
+ ds.post = function (request, callback){
+ assertJsonEquals([["POST", "", book]], request);
+ ds.post = function(request, callback){
+ assertJsonEquals([["POST", "", book]], request);
+ ds.post = function(){fail("too much recursion");};
+ callback(200, [{saved:"ok"}]);
+ };
+ callback(200, {$status_code:401});
+ };
+ book = ds.entity("Book")({name:"misko"});
+ book.$save();
+ ds.flush();
+ assertJsonEquals({saved:"ok"}, book);
+};
+
+DataStoreTest.prototype.testItShouldRemoveItemFromCollectionWhenDeleted = function() {
+ expectAsserts(6);
+ var ds = new DataStore();
+ ds.post = function(request, callback){
+ assertJsonEquals([["GET", "Book"]], request);
+ callback(200, [[{name:"Moby Dick", $id:123, $entity:'Book'}]]);
+ };
+ var Book = ds.entity("Book");
+ var books = Book.all();
+ ds.flush();
+ assertJsonEquals([[{name:"Moby Dick", $id:123, $entity:'Book'}]], ds._cache.$collections);
+ assertDefined(ds._cache['Book/123']);
+ var book = Book({$id:123});
+ ds.post = function(request, callback){
+ assertJsonEquals([["DELETE", "", book]], request);
+ callback(200, [book]);
+ };
+ ds.remove(book);
+ ds.flush();
+ assertUndefined(ds._cache['Book/123']);
+ assertJsonEquals([[]],ds._cache.$collections);
+};
+
+DataStoreTest.prototype.testItShouldAddToAll = function() {
+ expectAsserts(8);
+ var ds = new DataStore();
+ ds.post = function(request, callback){
+ assertJsonEquals([["GET", "Book"]], request);
+ callback(200, [[]]);
+ };
+ var Book = ds.entity("Book");
+ var books = Book.all();
+ assertEquals(0, books.length);
+ ds.flush();
+ var moby = Book({name:'moby'});
+ moby.$save();
+ ds.post = function(request, callback){
+ assertJsonEquals([["POST", "", moby]], request);
+ moby.$id = '123';
+ callback(200, [moby]);
+ };
+ ds.flush();
+ assertEquals(1, books.length);
+ assertEquals(moby, books[0]);
+
+ moby.$save();
+ ds.flush();
+ assertEquals(1, books.length);
+ assertEquals(moby, books[0]);
+};
+
+DataStoreTest.prototype.testItShouldReturnCreatedDocumentCountByUser = function(){
+ expectAsserts(2);
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "$users"]], request);
+ callback(200, [{misko:1, adam:1}]);
+ });
+ var users = datastore.documentCountsByUser();
+ assertJsonEquals({misko:1, adam:1}, users);
+};
+
+
+DataStoreTest.prototype.testItShouldReturnDocumentIdsForUeserByEntity = function(){
+ expectAsserts(2);
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "$users/misko@hevery.com"]], request);
+ callback(200, [{Book:["1"], Library:["2"]}]);
+ });
+ var users = datastore.userDocumentIdsByEntity("misko@hevery.com");
+ assertJsonEquals({Book:["1"], Library:["2"]}, users);
+};
+
+DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){
+ expectAsserts(7);
+ var log = "";
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "User/misko"]], request);
+ callback(200, [{$status_code:404}]);
+ });
+ var User = datastore.entity("User", {admin:false});
+ var user = User.loadOrCreate('misko', function(i){log+="cb "+i.$id+";";});
+ datastore.flush();
+ assertEquals("misko", user.$id);
+ assertEquals("User", user.$entity);
+ assertEquals(false, user.admin);
+ assertEquals("undefined", typeof user.$secret);
+ assertEquals("undefined", typeof user.$version);
+ assertEquals("cb misko;", log);
+};
+
+DataStoreTest.prototype.testItShouldReturnNewInstanceOn404 = function(){
+ var log = "";
+ var datastore = new DataStore(
+ function(request, callback){
+ assertJsonEquals([["GET", "User/misko"],["GET", "User/adam"]], request);
+ callback(200, [{$id:'misko'},{$id:'adam'}]);
+ });
+ var User = datastore.entity("User");
+ var users = User.loadMany(['misko', 'adam'], function(i){log+="cb "+toJson(i)+";";});
+ datastore.flush();
+ assertEquals("misko", users[0].$id);
+ assertEquals("adam", users[1].$id);
+ assertEquals('cb [{"$id":"misko"},{"$id":"adam"}];', log);
+};
+
+DataStoreTest.prototype.testItShouldCreateJoinAndQuery = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ var InvoiceWithCustomer = datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ var invoiceWithCustomer = InvoiceWithCustomer.query("invoice.month", 1);
+ assertEquals([], invoiceWithCustomer);
+ assertJsonEquals([["GET", "Invoice/month=1"]], datastore.bulkRequest);
+ var request = datastore.bulkRequest.shift();
+ request.$$callback([{$id:1, customer:1},{$id:2, customer:1},{$id:3, customer:3}]);
+ assertJsonEquals([["GET","Customer/1"],["GET","Customer/3"]], datastore.bulkRequest);
+ datastore.bulkRequest.shift().$$callback({$id:1});
+ datastore.bulkRequest.shift().$$callback({$id:3});
+ assertJsonEquals([
+ {invoice:{$id:1,customer:1},customer:{$id:1}},
+ {invoice:{$id:2,customer:1},customer:{$id:1}},
+ {invoice:{$id:3,customer:3},customer:{$id:3}}], invoiceWithCustomer);
+};
+
+DataStoreTest.prototype.testItShouldThrowIfMoreThanOneEntityIsPrimary = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Exactly one entity needs to be primary.", function(){
+ datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfLoopInReferences = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Infinite loop in join: invoice -> customer", function(){
+ datastore.join({
+ invoice:{join:Invoice, on:"customer.invoice"},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfReferenceToNonExistantJoin = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ assertThrows("Named entity 'x' is undefined.", function(){
+ datastore.join({
+ invoice:{join:Invoice, on:"x.invoice"},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ });
+};
+
+DataStoreTest.prototype.testItShouldThrowIfQueryOnNonPrimary = function() {
+ var datastore = new DataStore();
+ var Invoice = datastore.entity("Invoice");
+ var Customer = datastore.entity("Customer");
+ var InvoiceWithCustomer = datastore.join({
+ invoice:{join:Invoice},
+ customer:{join:Customer, on:"invoice.customer"}
+ });
+ assertThrows("Named entity 'customer' is not a primary entity.", function(){
+ InvoiceWithCustomer.query("customer.month", 1);
+ });
+};
diff --git a/test/moveToAngularCom/EntityDeclarationTest.js b/test/moveToAngularCom/EntityDeclarationTest.js
new file mode 100644
index 00000000..28986ea8
--- /dev/null
+++ b/test/moveToAngularCom/EntityDeclarationTest.js
@@ -0,0 +1,50 @@
+EntityDeclarationTest = TestCase('EntityDeclarationTest');
+
+EntityDeclarationTest.prototype.testEntityTypeOnly = function(){
+ expectAsserts(2);
+ var datastore = {entity:function(name){
+ assertEquals("Person", name);
+ }};
+ var scope = new Scope();
+ var init = scope.entity("Person", datastore);
+ assertEquals("", init);
+};
+
+EntityDeclarationTest.prototype.testWithDefaults = function(){
+ expectAsserts(4);
+ var datastore = {entity:function(name, init){
+ assertEquals("Person", name);
+ assertEquals("=a:", init.a);
+ assertEquals(0, init.b.length);
+ }};
+ var scope = new Scope();
+ var init = scope.entity('Person:{a:"=a:", b:[]}', datastore);
+ assertEquals("", init);
+};
+
+EntityDeclarationTest.prototype.testWithName = function(){
+ expectAsserts(2);
+ var datastore = {entity:function(name, init){
+ assertEquals("Person", name);
+ return function (){ return {}; };
+ }};
+ var scope = new Scope();
+ var init = scope.entity('friend=Person', datastore);
+ assertEquals("$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};", init);
+};
+
+EntityDeclarationTest.prototype.testMultipleEntities = function(){
+ expectAsserts(3);
+ var expect = ['Person', 'Book'];
+ var i=0;
+ var datastore = {entity:function(name, init){
+ assertEquals(expect[i], name);
+ i++;
+ return function (){ return {}; };
+ }};
+ var scope = new Scope();
+ var init = scope.entity('friend=Person;book=Book;', datastore);
+ assertEquals("$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};" +
+ "$anchor.book:{book=Book.load($anchor.book);book.$$anchor=\"book\";};",
+ init);
+};
diff --git a/test/moveToAngularCom/FileControllerTest.js b/test/moveToAngularCom/FileControllerTest.js
new file mode 100644
index 00000000..75c924e6
--- /dev/null
+++ b/test/moveToAngularCom/FileControllerTest.js
@@ -0,0 +1,98 @@
+FileControllerTest = TestCase('FileControllerTest');
+
+FileControllerTest.prototype.XtestOnSelectUpdateView = function(){
+ var view = jQuery('<span><a/><span/></span>');
+ var swf = {};
+ var controller = new FileController(view, null, swf);
+ swf.uploadFile = function(path){};
+ controller.select('A', 9, '9 bytes');
+ assertEquals(view.find('a').text(), "A");
+ assertEquals(view.find('span').text(), "9 bytes");
+};
+
+FileControllerTest.prototype.XtestUpdateModelView = function(){
+ var view = FileController.template('');
+ var input = $('<input name="value.input">');
+ var controller;
+ var scope = new Scope({value:{}, $binder:{updateView:function(){
+ controller.updateView(scope);
+ }}});
+ view.data('scope', scope);
+ controller = new FileController(view, 'value.input', null, "http://server_base");
+ var value = '{"text":"A", "size":123, "id":"890"}';
+ controller.uploadCompleteData(value);
+ controller.updateView(scope);
+ assertEquals(scope.get('value.input.text'), 'A');
+ assertEquals(scope.get('value.input.size'), 123);
+ assertEquals(scope.get('value.input.id'), '890');
+ assertEquals(scope.get('value.input.url'), 'http://server_base/_attachments/890/A');
+ assertEquals(view.find('a').text(), "A");
+ assertEquals(view.find('a').attr('href'), "http://server_base/_attachments/890/A");
+ assertEquals(view.find('span').text(), "123 bytes");
+};
+
+FileControllerTest.prototype.XtestFileUpload = function(){
+ expectAsserts(1);
+ var swf = {};
+ var controller = new FileController(null, null, swf, "http://server_base");
+ swf.uploadFile = function(path){
+ assertEquals("http://server_base/_attachments", path);
+ };
+ controller.name = "Name";
+ controller.upload();
+};
+
+FileControllerTest.prototype.XtestFileUploadNoFileIsNoop = function(){
+ expectAsserts(0);
+ var swf = {uploadFile:function(path){
+ fail();
+ }};
+ var controller = new FileController(null, swf);
+ controller.upload("basePath", null);
+};
+
+FileControllerTest.prototype.XtestRemoveAttachment = function(){
+ var doc = FileController.template();
+ var input = $('<input name="file">');
+ var scope = new Scope();
+ input.data('scope', scope);
+ var controller = new FileController(doc, 'file', null, null);
+ controller.updateView(scope);
+ assertEquals(false, doc.find('input').attr('checked'));
+
+ scope.set('file', {url:'url', size:123});
+ controller.updateView(scope);
+ assertEquals(true, doc.find('input').attr('checked'));
+
+ doc.find('input').attr('checked', false);
+ controller.updateModel(scope);
+ assertNull(scope.get('file'));
+
+ doc.find('input').attr('checked', true);
+ controller.updateModel(scope);
+ assertEquals('url', scope.get('file.url'));
+ assertEquals(123, scope.get('file.size'));
+};
+
+FileControllerTest.prototype.XtestShouldEmptyOutOnUndefined = function () {
+ var view = FileController.template('hello');
+ var controller = new FileController(view, 'abc', null, null);
+
+ var scope = new Scope();
+ scope.set('abc', {text: 'myname', url: 'myurl', size: 1234});
+
+ controller.updateView(scope);
+ assertEquals("myurl", view.find('a').attr('href'));
+ assertEquals("myname", view.find('a').text());
+ assertEquals(true, view.find('input').is(':checked'));
+ assertEquals("1.2 KB", view.find('span').text());
+
+ scope.set('abc', undefined);
+ controller.updateView(scope);
+ assertEquals("myurl", view.find('a').attr('href'));
+ assertEquals("myname", view.find('a').text());
+ assertEquals(false, view.find('input').is(':checked'));
+ assertEquals("1.2 KB", view.find('span').text());
+};
+
+
diff --git a/test/moveToAngularCom/MiscTest.js b/test/moveToAngularCom/MiscTest.js
new file mode 100644
index 00000000..aa0e1186
--- /dev/null
+++ b/test/moveToAngularCom/MiscTest.js
@@ -0,0 +1,35 @@
+BinderTest.prototype.testExpandEntityTagWithName = function(){
+ var c = this.compile('<div ng-entity="friend=Person"/>');
+ assertEquals(
+ '<div ng-entity="friend=Person" ng-watch="$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};"></div>',
+ sortedHtml(c.node));
+ assertEquals("Person", c.scope.$get("friend.$entity"));
+ assertEquals("friend", c.scope.$get("friend.$$anchor"));
+};
+
+BinderTest.prototype.testExpandSubmitButtonToAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save">');
+ assertTrue(html, html.indexOf('ng-action="$save()"') > 0 );
+ assertTrue(html, html.indexOf('ng-bind-attr="{"disabled":"{{$invalidWidgets}}"}"') > 0 );
+};
+
+BinderTest.prototype.testReplaceFileUploadWithSwf = function(){
+ expectAsserts(1);
+ var form = jQuery("body").append('<div id="testTag"><input type="file"></div>');
+ form.data('scope', new Scope());
+ var factory = {};
+ var binder = new Binder(form.get(0), factory, new MockLocation());
+ factory.createController = function(node){
+ assertEquals(node.attr('type'), 'file');
+ return {updateModel:function(){}};
+ };
+ binder.compile();
+ jQuery("#testTag").remove();
+};
+
+BinderTest.prototype.testExpandEntityTagWithDefaults = function(){
+ assertEquals(
+ '<div ng-entity="Person:{a:\"a\"}" ng-watch=""></div>',
+ this.compileToHtml('<div ng-entity=\'Person:{a:"a"}\'/>'));
+};
+
diff --git a/test/moveToAngularCom/ModelTest.js b/test/moveToAngularCom/ModelTest.js
new file mode 100644
index 00000000..dbd97778
--- /dev/null
+++ b/test/moveToAngularCom/ModelTest.js
@@ -0,0 +1,84 @@
+ModelTest = TestCase('ModelTest');
+
+ModelTest.prototype.testLoadSaveOperations = function(){
+ var m1 = new DataStore().entity('A')();
+ m1.a = 1;
+
+ var m2 = {b:1};
+
+ m1.$loadFrom(m2);
+
+ assertTrue(!m1.a);
+ assertEquals(m1.b, 1);
+};
+
+ModelTest.prototype.testLoadfromDoesNotClobberFunctions = function(){
+ var m1 = new DataStore().entity('A')();
+ m1.id = function(){return 'OK';};
+ m1.$loadFrom({id:null});
+ assertEquals(m1.id(), 'OK');
+
+ m1.b = 'OK';
+ m1.$loadFrom({b:function(){}});
+ assertEquals(m1.b, 'OK');
+};
+
+ModelTest.prototype.testDataStoreDoesNotGetClobbered = function(){
+ var ds = new DataStore();
+ var m = ds.entity('A')();
+ assertTrue(m.$$entity.datastore === ds);
+ m.$loadFrom({});
+ assertTrue(m.$$entity.datastore === ds);
+};
+
+ModelTest.prototype.testManagedModelDelegatesMethodsToDataStore = function(){
+ expectAsserts(7);
+ var datastore = new DataStore();
+ var model = datastore.entity("A", {a:1})();
+ var fn = {};
+ datastore.save = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === fn);
+ };
+ datastore.remove = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === fn);
+ };
+ datastore.load = function(instance, id, callback) {
+ assertTrue(model === instance);
+ assertTrue(id === "123");
+ assertTrue(callback === fn);
+ };
+ model.$save(fn);
+ model.$delete(fn);
+ model.$loadById("123", fn);
+};
+
+ModelTest.prototype.testManagedModelCanBeForcedToFlush = function(){
+ expectAsserts(6);
+ var datastore = new DataStore();
+ var model = datastore.entity("A", {a:1})();
+
+ datastore.save = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === undefined);
+ };
+ datastore.remove = function(instance, callback) {
+ assertTrue(model === instance);
+ assertTrue(callback === undefined);
+ };
+ datastore.flush = function(){
+ assertTrue(true);
+ };
+ model.$save(true);
+ model.$delete(true);
+};
+
+
+ModelTest.prototype.testItShouldMakeDeepCopyOfInitialValues = function (){
+ var initial = {a:[]};
+ var entity = new DataStore().entity("A", initial);
+ var model = entity();
+ model.a.push(1);
+ assertEquals(0, entity().a.length);
+};
diff --git a/test/moveToAngularCom/ServerTest.js b/test/moveToAngularCom/ServerTest.js
new file mode 100644
index 00000000..02fab84c
--- /dev/null
+++ b/test/moveToAngularCom/ServerTest.js
@@ -0,0 +1,42 @@
+ServerTest = TestCase("ServerTest");
+ServerTest.prototype.testBreakLargeRequestIntoPackets = function() {
+ var log = "";
+ var server = new Server("http://server", function(url){
+ log += "|" + url;
+ });
+ server.maxSize = 30;
+ server.uuid = "uuid";
+ server.request("POST", "/data/database", {}, function(code, r){
+ assertEquals(200, code);
+ assertEquals("response", r);
+ });
+ angularCallbacks.uuid0("response");
+ assertEquals(
+ "|http://server/$/uuid0/2/1?h=eyJtIjoiUE9TVCIsInAiOnt9LCJ1Ij" +
+ "|http://server/$/uuid0/2/2?h=oiL2RhdGEvZGF0YWJhc2UifQ==",
+ log);
+};
+
+ServerTest.prototype.testItShouldEncodeUsingUrlRules = function() {
+ var server = new Server("http://server");
+ assertEquals("fn5-fn5-", server.base64url("~~~~~~"));
+ assertEquals("fn5_fn5_", server.base64url("~~\u007f~~\u007f"));
+};
+
+FrameServerTest = TestCase("FrameServerTest");
+
+FrameServerTest.prototype = {
+ testRead:function(){
+ var window = {name:'$DATASET:"MyData"'};
+ var server = new FrameServer(window);
+ server.read();
+ assertEquals("MyData", server.data);
+ },
+ testWrite:function(){
+ var window = {};
+ var server = new FrameServer(window);
+ server.data = "TestData";
+ server.write();
+ assertEquals('$DATASET:"TestData"', window.name);
+ }
+};
diff --git a/test/moveToAngularCom/UsersTest.js b/test/moveToAngularCom/UsersTest.js
new file mode 100644
index 00000000..f0ff545a
--- /dev/null
+++ b/test/moveToAngularCom/UsersTest.js
@@ -0,0 +1,26 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+UsersTest = TestCase("UsersTest");
+
+UsersTest.prototype = {
+ setUp:function(){},
+
+ tearDown:function(){},
+
+ testItShouldFetchCurrentUser:function(){
+ expectAsserts(5);
+ var user;
+ var users = new Users({request:function(method, url, request, callback){
+ assertEquals("GET", method);
+ assertEquals("/account.json", url);
+ assertEquals("{}", toJson(request));
+ callback(200, {$status_code:200, user:{name:'misko'}});
+ }});
+ users.fetchCurrentUser(function(u){
+ user = u;
+ assertEquals("misko", u.name);
+ assertEquals("misko", users.current.name);
+ });
+ }
+
+};
diff --git a/test/moveToAngularCom/miscTest.js b/test/moveToAngularCom/miscTest.js
new file mode 100644
index 00000000..aa0e1186
--- /dev/null
+++ b/test/moveToAngularCom/miscTest.js
@@ -0,0 +1,35 @@
+BinderTest.prototype.testExpandEntityTagWithName = function(){
+ var c = this.compile('<div ng-entity="friend=Person"/>');
+ assertEquals(
+ '<div ng-entity="friend=Person" ng-watch="$anchor.friend:{friend=Person.load($anchor.friend);friend.$$anchor=\"friend\";};"></div>',
+ sortedHtml(c.node));
+ assertEquals("Person", c.scope.$get("friend.$entity"));
+ assertEquals("friend", c.scope.$get("friend.$$anchor"));
+};
+
+BinderTest.prototype.testExpandSubmitButtonToAction = function(){
+ var html = this.compileToHtml('<input type="submit" value="Save">');
+ assertTrue(html, html.indexOf('ng-action="$save()"') > 0 );
+ assertTrue(html, html.indexOf('ng-bind-attr="{"disabled":"{{$invalidWidgets}}"}"') > 0 );
+};
+
+BinderTest.prototype.testReplaceFileUploadWithSwf = function(){
+ expectAsserts(1);
+ var form = jQuery("body").append('<div id="testTag"><input type="file"></div>');
+ form.data('scope', new Scope());
+ var factory = {};
+ var binder = new Binder(form.get(0), factory, new MockLocation());
+ factory.createController = function(node){
+ assertEquals(node.attr('type'), 'file');
+ return {updateModel:function(){}};
+ };
+ binder.compile();
+ jQuery("#testTag").remove();
+};
+
+BinderTest.prototype.testExpandEntityTagWithDefaults = function(){
+ assertEquals(
+ '<div ng-entity="Person:{a:\"a\"}" ng-watch=""></div>',
+ this.compileToHtml('<div ng-entity=\'Person:{a:"a"}\'/>'));
+};
+
diff --git a/test/scenario/DSLSpec.js b/test/scenario/DSLSpec.js
new file mode 100644
index 00000000..5aac9752
--- /dev/null
+++ b/test/scenario/DSLSpec.js
@@ -0,0 +1,55 @@
+describe("DSL", function() {
+
+ var lastStep, executeStep, lastDocument;
+
+ beforeEach(function() {
+ lastStep = null;
+ $scenario = {
+ addStep: function(name, stepFunction) {
+ lastStep = { name:name, fn: stepFunction};
+ }
+ };
+ executeStep = function(step, html, callback) {
+ lastDocument =_jQuery('<div>' + html + '</div>');
+ _jQuery(document.body).append(lastDocument);
+ var specThis = {
+ testWindow: window,
+ testDocument: lastDocument
+ };
+ step.fn.call(specThis, callback || noop);
+ };
+ });
+
+ describe("input", function() {
+
+ var input = angular.scenario.dsl.input;
+ it('should enter', function() {
+ input('name').enter('John');
+ expect(lastStep.name).toEqual("Set input text of 'name' to 'John'");
+ executeStep(lastStep, '<input type="text" name="name" />');
+ expect(lastDocument.find('input').val()).toEqual('John');
+ });
+
+ it('should select', function() {
+ input('gender').select('female');
+ expect(lastStep.name).toEqual("Select radio 'gender' to 'female'");
+ executeStep(lastStep,
+ '<input type="radio" name="0@gender" value="male" checked/>' +
+ '<input type="radio" name="0@gender" value="female"/>');
+ expect(lastDocument.find(':radio:checked').length).toEqual(1);
+ expect(lastDocument.find(':radio:checked').val()).toEqual('female');
+ });
+ });
+
+ describe('expect', function() {
+ var dslExpect = angular.scenario.dsl.expect;
+ describe('repeater', function() {
+ it('should check the count of repeated elements', function() {
+ dslExpect.repeater('.repeater-row').count.toEqual(2);
+ expect(lastStep.name).toEqual("Expect that there are 2 items in Repeater with selector '.repeater-row'");
+ var html = "<div class='repeater-row'>a</div><div class='repeater-row'>b</div>";
+ executeStep(lastStep, html);
+ });
+ });
+ });
+});
diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js
new file mode 100644
index 00000000..ca6e8eb2
--- /dev/null
+++ b/test/scenario/RunnerSpec.js
@@ -0,0 +1,190 @@
+describe('Runner', function(){
+ var scenario, runner, log, Describe, It, $scenario, body;
+
+ function logger(text) {
+ return function(done){
+ log += text;
+ (done||noop)();
+ };
+ }
+
+ beforeEach(function(){
+ log = '';
+ scenario = {};
+ body = _jQuery('<div></div>');
+ runner = new angular.scenario.Runner(scenario, _jQuery);
+ Describe = scenario.describe;
+ BeforeEach = scenario.beforeEach;
+ AfterEach = scenario.afterEach;
+ It = scenario.it;
+ $scenario = scenario.$scenario;
+ });
+
+ describe('describe', function(){
+ it('should consume the describe functions', function(){
+ Describe('describe name', logger('body'));
+
+ expect(log).toEqual('body');
+ });
+
+ describe('it', function(){
+ it('should consume it', function(){
+ Describe('describe name', function(){
+ It('should text', logger('body'));
+ });
+ expect(log).toEqual('body');
+ var spec = $scenario.specs['describe name: it should text'];
+ expect(spec.steps).toEqual([]);
+ expect(spec.name).toEqual('describe name: it should text');
+ });
+
+ it('should complain on duplicate it', function() {
+ // WRITE ME!!!!
+ });
+
+ it('should create a failing step if there is a javascript error', function(){
+ var spec;
+ Describe('D1', function(){
+ It('I1', function(){
+ spec = $scenario.currentSpec;
+ throw {message: 'blah'};
+ });
+ });
+ var step = spec.steps[0];
+ expect(step.name).toEqual('blah');
+ try {
+ step.fn();
+ fail();
+ } catch (e) {
+ expect(e.message).toEqual('blah');
+ };
+ });
+ });
+
+ describe('beforeEach', function() {
+ it('should execute beforeEach before every it', function() {
+ Describe('describe name', function(){
+ BeforeEach(logger('before;'));
+ It('should text', logger('body;'));
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('before;body;before;body2;');
+ });
+ });
+ describe('afterEach', function() {
+ it('should execute afterEach after every it', function() {
+ Describe('describe name', function(){
+ AfterEach(logger('after;'));
+ It('should text', logger('body;'));
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('body;after;body2;after;');
+ });
+
+ it('should always execute afterEach after every it', function() {
+ Describe('describe name', function(){
+ AfterEach(logger('after;'));
+ It('should text', function() {
+ log = 'body;';
+ throw "MyError";
+ });
+ It('should text2', logger('body2;'));
+ });
+ expect(log).toEqual('body;after;body2;after;');
+ });
+ });
+ });
+
+ describe('steps building', function(){
+ it('should queue steps', function(){
+ function step(){};
+ Describe('name', function(){
+ It('should', function(){
+ $scenario.addStep('stepname', step);
+ });
+ });
+ expect($scenario.specs['name: it should'].steps).toEqual([{name:'stepname', fn:step}]);
+ });
+ });
+
+ describe('execution', function(){
+ it('should execute the queued steps', function(){
+ var next, firstThis, secondThis, doneThis, spec;
+ $scenario.specs['spec'] = {
+ steps: [
+ {name:'step1', fn: function(done) {
+ next = done;
+ log += 'first;';
+ firstThis = this;
+ }},
+ {name:'step2', fn:function(done){
+ next = done;
+ log += 'second;';
+ secondThis = this;
+ }}
+ ]
+ };
+
+ spec = $scenario.execute('spec', function(done){
+ log += 'done;';
+ doneThis = this;
+ });
+ expect(log).toEqual('first;');
+ next();
+ expect(log).toEqual('first;second;');
+ next();
+ expect(log).toEqual('first;second;done;');
+ expect(spec).not.toEqual(window);
+ expect(spec).toEqual(firstThis);
+ expect(spec).toEqual(secondThis);
+ expect(spec).toEqual(doneThis);
+
+ expect(spec.result.failed).toEqual(false);
+ expect(spec.result.finished).toEqual(true);
+ expect(spec.result.error).toBeUndefined();
+ expect(spec.result.passed).toEqual(true);
+ });
+
+ it('should handle exceptions in a step', function(){
+ $scenario.specs['spec'] = {
+ steps: [
+ {name:'error', fn:function(done) {
+ throw "MyError";
+ }}
+ ]
+ };
+
+ var spec = $scenario.execute('spec');
+
+ expect(spec.result.passed).toEqual(false);
+ expect(spec.result.failed).toEqual(true);
+ expect(spec.result.finished).toEqual(true);
+ expect(spec.result.error).toEqual("MyError");
+ });
+ });
+
+ describe('run', function(){
+ var next;
+ it('should execute all specs', function(){
+ Describe('d1', function(){
+ It('it1', function(){ $scenario.addStep('s1', logger('s1,')); });
+ It('it2', function(){
+ $scenario.addStep('s2', logger('s2,'));
+ $scenario.addStep('s2.2', function(done){ next = done; });
+ });
+ });
+ Describe('d2', function(){
+ It('it3', function(){ $scenario.addStep('s3', logger('s3,')); });
+ It('it4', function(){ $scenario.addStep('s4', logger('s4,')); });
+ });
+
+ $scenario.run(body);
+
+ expect(log).toEqual('s1,s2,');
+ next();
+ expect(log).toEqual('s1,s2,s3,s4,');
+
+ });
+ });
+
+}); \ No newline at end of file
diff --git a/test/servicesSpec.js b/test/servicesSpec.js
new file mode 100644
index 00000000..f679a39b
--- /dev/null
+++ b/test/servicesSpec.js
@@ -0,0 +1,362 @@
+describe("service", function(){
+ var scope, $xhrError, $log;
+
+ beforeEach(function(){
+ $xhrError = jasmine.createSpy('$xhr.error');
+ $log = {};
+ scope = createScope(null, angularService, {
+ '$xhr.error': $xhrError,
+ '$log': $log
+ });
+ });
+
+ afterEach(function(){
+ if (scope && scope.$element)
+ scope.$element.remove();
+ });
+
+
+
+ it("should inject $window", function(){
+ expect(scope.$window).toEqual(window);
+ });
+
+ xit('should add stylesheets', function(){
+ scope.$document = {
+ getElementsByTagName: function(name){
+ expect(name).toEqual('LINK');
+ return [];
+ }
+ };
+ scope.$document.addStyleSheet('css/angular.css');
+ });
+
+ describe("$log", function(){
+ it('should use console if present', function(){
+ function log(){};
+ function warn(){};
+ function info(){};
+ function error(){};
+ var scope = createScope(null, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}});
+ expect(scope.$log.log).toEqual(log);
+ expect(scope.$log.warn).toEqual(warn);
+ expect(scope.$log.info).toEqual(info);
+ expect(scope.$log.error).toEqual(error);
+ });
+
+ it('should use console.log if other not present', function(){
+ function log(){};
+ var scope = createScope(null, angularService, {$window: {console:{log:log}}});
+ expect(scope.$log.log).toEqual(log);
+ expect(scope.$log.warn).toEqual(log);
+ expect(scope.$log.info).toEqual(log);
+ expect(scope.$log.error).toEqual(log);
+ });
+
+ it('should use noop if no console', function(){
+ var scope = createScope(null, angularService, {$window: {}});
+ expect(scope.$log.log).toEqual(noop);
+ expect(scope.$log.warn).toEqual(noop);
+ expect(scope.$log.info).toEqual(noop);
+ expect(scope.$log.error).toEqual(noop);
+ });
+ });
+
+ describe("$location", function(){
+ it("should inject $location", function(){
+ scope.$location.parse('http://host:123/p/a/t/h.html?query=value#path?key=value');
+ expect(scope.$location.href).toEqual("http://host:123/p/a/t/h.html?query=value#path?key=value");
+ expect(scope.$location.protocol).toEqual("http");
+ expect(scope.$location.host).toEqual("host");
+ expect(scope.$location.port).toEqual("123");
+ expect(scope.$location.path).toEqual("/p/a/t/h.html");
+ expect(scope.$location.search).toEqual({query:'value'});
+ expect(scope.$location.hash).toEqual('path?key=value');
+ expect(scope.$location.hashPath).toEqual('path');
+ expect(scope.$location.hashSearch).toEqual({key:'value'});
+
+ scope.$location.hashPath = 'page=http://path';
+ scope.$location.hashSearch = {k:'a=b'};
+
+ expect(scope.$location.toString()).toEqual('http://host:123/p/a/t/h.html?query=value#page=http://path?k=a%3Db');
+ });
+
+ it('should parse file://', function(){
+ scope.$location.parse('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
+ expect(scope.$location.href).toEqual("file:///Users/Shared/misko/work/angular.js/scenario/widgets.html");
+ expect(scope.$location.protocol).toEqual("file");
+ expect(scope.$location.host).toEqual("");
+ expect(scope.$location.port).toEqual(null);
+ expect(scope.$location.path).toEqual("/Users/Shared/misko/work/angular.js/scenario/widgets.html");
+ expect(scope.$location.search).toEqual({});
+ expect(scope.$location.hash).toEqual('');
+ expect(scope.$location.hashPath).toEqual('');
+ expect(scope.$location.hashSearch).toEqual({});
+
+ expect(scope.$location.toString()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html#');
+ });
+
+ it('should update url on hash change', function(){
+ scope.$location.parse('http://server/#path?a=b');
+ scope.$location.hash = '';
+ expect(scope.$location.toString()).toEqual('http://server/#');
+ expect(scope.$location.hashPath).toEqual('');
+ });
+
+ it('should update url on hashPath change', function(){
+ scope.$location.parse('http://server/#path?a=b');
+ scope.$location.hashPath = '';
+ expect(scope.$location.toString()).toEqual('http://server/#?a=b');
+ expect(scope.$location.hash).toEqual('?a=b');
+ });
+
+ it('should update hash before any processing', function(){
+ var scope = compile('<div>');
+ var log = '';
+ scope.$watch('$location.hash', function(){
+ log += this.$location.hashPath + ';';
+ });
+ expect(log).toEqual(';');
+
+ log = '';
+ scope.$location.hash = '/abc';
+ scope.$eval();
+ expect(log).toEqual('/abc;');
+ });
+
+ it("should parse url which contains - in host", function(){
+ scope.$location.parse('http://a-b1.c-d.09/path');
+ expect(scope.$location.href).toEqual('http://a-b1.c-d.09/path');
+ expect(scope.$location.protocol).toEqual('http');
+ expect(scope.$location.host).toEqual('a-b1.c-d.09');
+ expect(scope.$location.path).toEqual('/path');
+ });
+
+ });
+
+ describe("$invalidWidgets", function(){
+ it("should count number of invalid widgets", function(){
+ var scope = compile('<input name="price" ng-required ng-validate="number"></input>').$init();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+ scope.price = 123;
+ scope.$eval();
+ expect(scope.$invalidWidgets.length).toEqual(0);
+ scope.$element.remove();
+ scope.price = 'abc';
+ scope.$eval();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+
+ jqLite(document.body).append(scope.$element);
+ scope.$invalidWidgets.clearOrphans();
+ expect(scope.$invalidWidgets.length).toEqual(1);
+
+ jqLite(document.body).html('');
+ scope.$invalidWidgets.clearOrphans();
+ expect(scope.$invalidWidgets.length).toEqual(0);
+ });
+ });
+
+
+ describe("$route", function(){
+ it('should route and fire change event', function(){
+ var log = '';
+ function BookChapter() {
+ this.log = '<init>';
+ }
+ BookChapter.prototype.init = function(){
+ log += 'init();';
+ };
+ var scope = compile('<div></div>').$init();
+ scope.$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
+ scope.$route.when('/Blank');
+ scope.$route.onChange(function(){
+ log += 'onChange();';
+ });
+ scope.$location.parse('http://server#/Book/Moby/Chapter/Intro?p=123');
+ scope.$eval();
+ expect(log).toEqual('onChange();init();');
+ expect(scope.$route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
+ expect(scope.$route.current.scope.log).toEqual('<init>');
+ var lastId = scope.$route.current.scope.$id;
+
+ log = '';
+ scope.$location.parse('http://server#/Blank?ignore');
+ scope.$eval();
+ expect(log).toEqual('onChange();');
+ expect(scope.$route.current.params).toEqual({ignore:true});
+ expect(scope.$route.current.scope.$id).not.toEqual(lastId);
+
+ log = '';
+ scope.$location.parse('http://server#/NONE');
+ scope.$eval();
+ expect(log).toEqual('onChange();');
+ expect(scope.$route.current).toEqual(null);
+
+ scope.$route.when('/NONE', {template:'instant update'});
+ scope.$eval();
+ expect(scope.$route.current.template).toEqual('instant update');
+ });
+ });
+
+ describe('$resource', function(){
+ it('should publish to root scope', function(){
+ expect(scope.$resource).toBeTruthy();
+ });
+ });
+
+ describe('$xhr', function(){
+ var log, xhr;
+ function callback(code, response) {
+ expect(code).toEqual(200);
+ log = log + toJson(response) + ';';
+ }
+
+ beforeEach(function(){
+ log = '';
+ xhr = scope.$browser.xhr;
+ });
+
+ it('should forward the request to $browser and decode JSON', function(){
+ xhr.expectGET('/reqGET').respond('first');
+ xhr.expectGET('/reqGETjson').respond('["second"]');
+ xhr.expectPOST('/reqPOST', {post:'data'}).respond('third');
+
+ scope.$xhr('GET', '/reqGET', null, callback);
+ scope.$xhr('GET', '/reqGETjson', null, callback);
+ scope.$xhr('POST', '/reqPOST', {post:'data'}, callback);
+
+ xhr.flush();
+
+ expect(log).toEqual('"third";["second"];"first";');
+ });
+
+ it('should handle non 200 status codes by forwarding to error handler', function(){
+ xhr.expectPOST('/req', 'MyData').respond(500, 'MyError');
+ scope.$xhr('POST', '/req', 'MyData', callback);
+ xhr.flush();
+ var cb = $xhrError.mostRecentCall.args[0].callback;
+ expect(typeof cb).toEqual('function');
+ expect($xhrError).wasCalledWith(
+ {url:'/req', method:'POST', data:'MyData', callback:cb},
+ {status:500, body:'MyError'});
+ });
+
+ it('should handle exceptions in callback', function(){
+ $log.error = jasmine.createSpy('$log.error');
+ xhr.expectGET('/reqGET').respond('first');
+ scope.$xhr('GET', '/reqGET', null, function(){ throw "MyException"; });
+ xhr.flush();
+
+ expect($log.error).wasCalledWith("MyException");
+ });
+
+ describe('bulk', function(){
+ it('should collect requests', function(){
+ scope.$xhr.bulk.urls["/"] = {match:/.*/};
+ scope.$xhr.bulk('GET', '/req1', null, callback);
+ scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
+
+ xhr.expectPOST('/', {
+ requests:[{method:'GET', url:'/req1', data: null},
+ {method:'POST', url:'/req2', data:{post:'data'} }]
+ }).respond([
+ {status:200, response:'first'},
+ {status:200, response:'second'}
+ ]);
+ scope.$xhr.bulk.flush(function(){ log += 'DONE';});
+ xhr.flush();
+ expect(log).toEqual('"first";"second";DONE');
+ });
+
+ it('should handle non 200 status code by forwarding to error handler', function(){
+ scope.$xhr.bulk.urls['/'] = {match:/.*/};
+ scope.$xhr.bulk('GET', '/req1', null, callback);
+ scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
+
+ xhr.expectPOST('/', {
+ requests:[{method:'GET', url:'/req1', data: null},
+ {method:'POST', url:'/req2', data:{post:'data'} }]
+ }).respond([
+ {status:404, response:'NotFound'},
+ {status:200, response:'second'}
+ ]);
+ scope.$xhr.bulk.flush(function(){ log += 'DONE';});
+ xhr.flush();
+
+ expect($xhrError).wasCalled();
+ var cb = $xhrError.mostRecentCall.args[0].callback;
+ expect(typeof cb).toEqual('function');
+ expect($xhrError).wasCalledWith(
+ {url:'/req1', method:'GET', data:null, callback:cb},
+ {status:404, response:'NotFound'});
+
+ expect(log).toEqual('"second";DONE');
+ });
+ });
+
+ describe('cache', function(){
+ var cache;
+ beforeEach(function(){ cache = scope.$xhr.cache; });
+
+ it('should cache requests', function(){
+ xhr.expectGET('/url').respond('first');
+ cache('GET', '/url', null, callback);
+ xhr.flush();
+ xhr.expectGET('/url').respond('ERROR');
+ cache('GET', '/url', null, callback);
+ xhr.flush();
+ expect(log).toEqual('"first";"first";');
+ cache('GET', '/url', null, callback, false);
+ xhr.flush();
+ expect(log).toEqual('"first";"first";"first";');
+ });
+
+ it('should first return cache request, then return server request', function(){
+ xhr.expectGET('/url').respond('first');
+ cache('GET', '/url', null, callback, true);
+ xhr.flush();
+ xhr.expectGET('/url').respond('ERROR');
+ cache('GET', '/url', null, callback, true);
+ expect(log).toEqual('"first";"first";');
+ xhr.flush();
+ expect(log).toEqual('"first";"first";"ERROR";');
+ });
+
+ it('should serve requests from cache', function(){
+ cache.data.url = {value:'123'};
+ cache('GET', 'url', null, callback);
+ expect(log).toEqual('"123";');
+ cache('GET', 'url', null, callback, false);
+ expect(log).toEqual('"123";"123";');
+ });
+
+ it('should keep track of in flight requests and request only once', function(){
+ scope.$xhr.bulk.urls['/bulk'] = {
+ match:function(url){
+ return url == '/url';
+ }
+ };
+ xhr.expectPOST('/bulk', {
+ requests:[{method:'GET', url:'/url', data: null}]
+ }).respond([
+ {status:200, response:'123'}
+ ]);
+ cache('GET', '/url', null, callback);
+ cache('GET', '/url', null, callback);
+ cache.delegate.flush();
+ xhr.flush();
+ expect(log).toEqual('"123";"123";');
+ });
+
+ it('should clear cache on non GET', function(){
+ xhr.expectPOST('abc', {}).respond({});
+ cache.data.url = {value:123};
+ cache('POST', 'abc', {});
+ expect(cache.data.url).toBeUndefined();
+ });
+ });
+
+ });
+
+
+});
diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js
new file mode 100644
index 00000000..d621b1f1
--- /dev/null
+++ b/test/testabilityPatch.js
@@ -0,0 +1,180 @@
+jstd = jstestdriver;
+dump = bind(jstd.console, jstd.console.log);
+
+beforeEach(function(){
+ this.addMatchers({
+ toBeInvalid: function(){
+ var element = jqLite(this.actual);
+ var hasClass = element.hasClass('ng-validation-error');
+ var validationError = element.attr('ng-validation-error');
+ this.message = function(){
+ if (!hasClass)
+ return "Expected class 'ng-validation-error' not found.";
+ return "Expected an error message, but none was found.";
+ };
+ return hasClass && validationError;
+ },
+
+ toBeValid: function(){
+ var element = jqLite(this.actual);
+ var hasClass = element.hasClass('ng-validation-error');
+ this.message = function(){
+ return "Expected to not have class 'ng-validation-error' but found.";
+ };
+ return !hasClass;
+ }
+ });
+});
+
+function nakedExpect(obj) {
+ return expect(angular.fromJson(angular.toJson(obj)));
+}
+
+function childNode(element, index) {
+ return jqLite(element[0].childNodes[index]);
+}
+
+extend(angular, {
+ 'bind': bind,
+ 'compile': compile,
+ 'copy': copy,
+ 'element': jqLite,
+ 'extend': extend,
+ 'foreach': foreach,
+ 'identity':identity,
+ 'isUndefined': isUndefined,
+ 'isDefined': isDefined,
+ 'isObject': isObject,
+ 'isString': isString,
+ 'isFunction': isFunction,
+ 'isNumber': isNumber,
+ 'isArray': isArray,
+ 'noop':noop,
+ 'scope': createScope
+});
+
+
+function sortedHtml(element) {
+ var html = "";
+ foreach(element, function toString(node) {
+ if (node.nodeName == "#text") {
+ html += escapeHtml(node.nodeValue);
+ } else {
+ html += '<' + node.nodeName.toLowerCase();
+ var attributes = node.attributes || [];
+ var attrs = [];
+ for(var i=0; i<attributes.length; i++) {
+ var attr = attributes[i];
+ if(attr.name.match(/^ng-/) ||
+ attr.value &&
+ attr.value !='null' &&
+ attr.value !='auto' &&
+ attr.value !='false' &&
+ attr.value !='inherit' &&
+ attr.value !='0' &&
+ attr.name !='loop' &&
+ attr.name !='complete' &&
+ attr.name !='maxLength' &&
+ attr.name !='size' &&
+ attr.name !='start' &&
+ attr.name !='tabIndex' &&
+ attr.name !='style' &&
+ attr.name.substr(0, 6) != 'jQuery') {
+ // in IE we need to check for all of these.
+ if (!/ng-\d+/.exec(attr.name))
+ attrs.push(' ' + attr.name + '="' + attr.value + '"');
+ }
+ }
+ attrs.sort();
+ html += attrs.join('');
+ if (node.style) {
+ var style = [];
+ if (node.style.cssText) {
+ foreach(node.style.cssText.split(';'), function(value){
+ value = trim(value);
+ if (value) {
+ style.push(lowercase(value));
+ }
+ });
+ }
+ for(var css in node.style){
+ var value = node.style[css];
+ if (isString(value) && isString(css) && css != 'cssText' && value && (1*css != css)) {
+ var text = lowercase(css + ': ' + value);
+ if (value != 'false' && indexOf(style, text) == -1) {
+ style.push(text);
+ }
+ }
+ }
+ style.sort();
+ var tmp = style;
+ style = [];
+ foreach(tmp, function(value){
+ if (!value.match(/^max[^\-]/))
+ style.push(value);
+ });
+ if (style.length) {
+ html += ' style="' + style.join('; ') + ';"';
+ }
+ }
+ html += '>';
+ var children = node.childNodes;
+ for(var j=0; j<children.length; j++) {
+ toString(children[j]);
+ }
+ html += '</' + node.nodeName.toLowerCase() + '>';
+ }
+ });
+ return html;
+}
+
+function isCssVisible(node) {
+ var display = node.css('display');
+ if (display == 'block') display = "";
+ return display != 'none';
+}
+
+function assertHidden(node) {
+ assertFalse("Node should be hidden but vas visible: " + sortedHtml(node), isCssVisible(node));
+}
+
+function assertVisible(node) {
+ assertTrue("Node should be visible but vas hidden: " + sortedHtml(node), isCssVisible(node));
+}
+
+function assertJsonEquals(expected, actual) {
+ assertEquals(toJson(expected), toJson(actual));
+}
+
+function assertUndefined(value) {
+ assertEquals('undefined', typeof value);
+}
+
+function assertDefined(value) {
+ assertTrue(toJson(value), !!value);
+}
+
+function assertThrows(error, fn){
+ var exception = null;
+ try {
+ fn();
+ } catch(e) {
+ exception = e;
+ }
+ if (!exception) {
+ fail("Expecting exception, none thrown");
+ }
+ assertEquals(error, exception);
+}
+
+log = noop;
+error = noop;
+
+function click(element) {
+ element = jqLite(element);
+ if ( msie &&
+ nodeName(element) == 'INPUT' && (lowercase(element.attr('type')) == 'radio' || lowercase(element.attr('type')) == 'checkbox')) {
+ element[0].checked = ! element[0].checked;
+ }
+ JQLite.prototype.trigger.call(element, 'click');
+}
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js
new file mode 100644
index 00000000..a053090e
--- /dev/null
+++ b/test/widgetsSpec.js
@@ -0,0 +1,441 @@
+describe("widget", function(){
+ var compile, element, scope;
+
+ beforeEach(function() {
+ scope = null;
+ element = null;
+ var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
+ compile = function(html, before) {
+ element = jqLite(html);
+ scope = compiler.compile(element)(element);
+ (before||noop).apply(scope);
+ scope.$init();
+ };
+ });
+
+ afterEach(function(){
+ if (element && element.dealoc) element.dealoc();
+ expect(size(jqCache)).toEqual(0);
+ });
+
+ describe("input", function(){
+
+ describe("text", function(){
+ it('should input-text auto init and handle keyup/change events', function(){
+ compile('<input type="Text" name="name" value="Misko" ng-change="count = count + 1" ng-init="count=0"/>');
+ expect(scope.$get('name')).toEqual("Misko");
+ expect(scope.$get('count')).toEqual(0);
+
+ scope.$set('name', 'Adam');
+ scope.$eval();
+ expect(element.val()).toEqual("Adam");
+
+ element.val('Shyam');
+ element.trigger('keyup');
+ expect(scope.$get('name')).toEqual('Shyam');
+ expect(scope.$get('count')).toEqual(1);
+
+ element.val('Kai');
+ element.trigger('change');
+ expect(scope.$get('name')).toEqual('Kai');
+ expect(scope.$get('count')).toEqual(2);
+ });
+
+ describe("ng-format", function(){
+
+ it("should format text", function(){
+ compile('<input type="Text" name="list" value="a,b,c" ng-format="list"/>');
+ expect(scope.$get('list')).toEqual(['a', 'b', 'c']);
+
+ scope.$set('list', ['x', 'y', 'z']);
+ scope.$eval();
+ expect(element.val()).toEqual("x, y, z");
+
+ element.val('1, 2, 3');
+ element.trigger('keyup');
+ expect(scope.$get('list')).toEqual(['1', '2', '3']);
+ });
+
+ it("should come up blank if null", function(){
+ compile('<input type="text" name="age" ng-format="number"/>', function(){
+ scope.age = null;
+ });
+ expect(scope.age).toBeNull();
+ expect(scope.$element[0].value).toEqual('');
+ });
+
+ it("should show incorect text while number does not parse", function(){
+ compile('<input type="text" name="age" ng-format="number"/>');
+ scope.age = 123;
+ scope.$eval();
+ scope.$element.val('123X');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('123X');
+ expect(scope.age).toEqual(123);
+ expect(scope.$element).toBeInvalid();
+ });
+
+ it("should clober incorect text if model changes", function(){
+ compile('<input type="text" name="age" ng-format="number" value="123X"/>');
+ scope.age = 456;
+ scope.$eval();
+ expect(scope.$element.val()).toEqual('456');
+ });
+
+ it("should not clober text if model changes doe to itself", function(){
+ compile('<input type="text" name="list" ng-format="list" value="a"/>');
+
+ scope.$element.val('a ');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a ');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a ,');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a ,');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a , ');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a , ');
+ expect(scope.list).toEqual(['a']);
+
+ scope.$element.val('a , b');
+ scope.$element.trigger('change');
+ expect(scope.$element.val()).toEqual('a , b');
+ expect(scope.list).toEqual(['a', 'b']);
+ });
+
+ it("should come up blank when no value specifiend", function(){
+ compile('<input type="text" name="age" ng-format="number"/>');
+ scope.$eval();
+ expect(scope.$element.val()).toEqual('');
+ expect(scope.age).toEqual(null);
+ });
+
+ });
+
+ describe("checkbox", function(){
+ it("should format booleans", function(){
+ compile('<input type="checkbox" name="name"/>', function(){
+ scope.name = false;
+ });
+ expect(scope.name).toEqual(false);
+ expect(scope.$element[0].checked).toEqual(false);
+ });
+
+ it('should support type="checkbox"', function(){
+ compile('<input type="checkBox" name="checkbox" checked ng-change="action = true"/>');
+ expect(scope.checkbox).toEqual(true);
+ click(element);
+ expect(scope.checkbox).toEqual(false);
+ expect(scope.action).toEqual(true);
+ click(element);
+ expect(scope.checkbox).toEqual(true);
+ });
+
+ it("should use ng-format", function(){
+ angularFormatter('testFormat', {
+ parse: function(value){
+ return value ? "Worked" : "Failed";
+ },
+
+ format: function(value) {
+ if (value == undefined) return value;
+ return value == "Worked";
+ }
+
+ });
+ compile('<input type="checkbox" name="state" ng-format="testFormat" checked/>');
+ expect(scope.state).toEqual("Worked");
+ expect(scope.$element[0].checked).toEqual(true);
+
+ click(scope.$element);
+ expect(scope.state).toEqual("Failed");
+ expect(scope.$element[0].checked).toEqual(false);
+
+ scope.state = "Worked";
+ scope.$eval();
+ expect(scope.state).toEqual("Worked");
+ expect(scope.$element[0].checked).toEqual(true);
+ });
+ });
+
+ describe("ng-validate", function(){
+ it("should process ng-validate", function(){
+ compile('<input type="text" name="price" value="abc" ng-validate="number"/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Not a number');
+
+ scope.$set('price', '123');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ element.val('x');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Not a number');
+ });
+
+ it('should not blow up for validation with bound attributes', function() {
+ compile('<input type="text" name="price" boo="{{abc}}" ng-required/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ scope.$set('price', '123');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should not call validator if undefined/empty", function(){
+ var lastValue = "NOT_CALLED";
+ angularValidator.myValidator = function(value){lastValue = value;};
+ compile('<input type="text" name="url" ng-validate="myValidator"/>');
+ expect(lastValue).toEqual("NOT_CALLED");
+
+ scope.url = 'http://server';
+ scope.$eval();
+ expect(lastValue).toEqual("http://server");
+
+ delete angularValidator.myValidator;
+ });
+ });
+ });
+
+ it("should ignore disabled widgets", function(){
+ compile('<input type="text" name="price" ng-required disabled/>');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should ignore readonly widgets", function(){
+ compile('<input type="text" name="price" ng-required readonly/>');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should process ng-required", function(){
+ compile('<input type="text" name="price" ng-required/>');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ scope.$set('price', 'xxx');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ element.val('');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+ });
+
+ it('should allow conditions on ng-required', function() {
+ compile('<input type="text" name="price" ng-required="ineedz"/>');
+ scope.$set('ineedz', false);
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ scope.$set('price', 'xxx');
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+
+ scope.$set('price', '');
+ scope.$set('ineedz', true);
+ scope.$eval();
+ expect(element.hasClass('ng-validation-error')).toBeTruthy();
+ expect(element.attr('ng-validation-error')).toEqual('Required');
+
+ element.val('abc');
+ element.trigger('keyup');
+ expect(element.hasClass('ng-validation-error')).toBeFalsy();
+ expect(element.attr('ng-validation-error')).toBeFalsy();
+ });
+
+ it("should process ng-required2", function() {
+ compile('<textarea name="name">Misko</textarea>');
+ expect(scope.$get('name')).toEqual("Misko");
+
+ scope.$set('name', 'Adam');
+ scope.$eval();
+ expect(element.val()).toEqual("Adam");
+
+ element.val('Shyam');
+ element.trigger('keyup');
+ expect(scope.$get('name')).toEqual('Shyam');
+
+ element.val('Kai');
+ element.trigger('change');
+ expect(scope.$get('name')).toEqual('Kai');
+ });
+
+ it('should call ng-change on button click', function(){
+ compile('<input type="button" value="Click Me" ng-change="clicked = true"/>');
+ click(element);
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ it('should support button alias', function(){
+ compile('<button ng-change="clicked = true">Click Me</button>');
+ click(element);
+ expect(scope.$get('clicked')).toEqual(true);
+ });
+
+ describe('radio', function(){
+
+ it('should support type="radio"', function(){
+ compile('<div>' +
+ '<input type="radio" name="chose" value="A" ng-change="clicked = 1"/>' +
+ '<input type="radio" name="chose" value="B" checked ng-change="clicked = 2"/>' +
+ '<input type="radio" name="chose" value="C" ng-change="clicked = 3"/>' +
+ '</div>');
+ var a = element[0].childNodes[0];
+ var b = element[0].childNodes[1];
+ expect(b.name.split('@')[1]).toEqual('chose');
+ expect(scope.chose).toEqual('B');
+ scope.chose = 'A';
+ scope.$eval();
+ expect(a.checked).toEqual(true);
+
+ scope.chose = 'B';
+ scope.$eval();
+ expect(a.checked).toEqual(false);
+ expect(b.checked).toEqual(true);
+ expect(scope.clicked).not.toBeDefined();
+
+ click(a);
+ expect(scope.chose).toEqual('A');
+ expect(scope.clicked).toEqual(1);
+ });
+
+ it('should honor model over html checked keyword after', function(){
+ compile('<div>' +
+ '<input type="radio" name="choose" value="A""/>' +
+ '<input type="radio" name="choose" value="B" checked/>' +
+ '<input type="radio" name="choose" value="C"/>' +
+ '</div>', function(){
+ this.choose = 'C';
+ });
+
+ expect(scope.choose).toEqual('C');
+ });
+
+ it('should honor model over html checked keyword before', function(){
+ compile('<div>' +
+ '<input type="radio" name="choose" value="A""/>' +
+ '<input type="radio" name="choose" value="B" checked/>' +
+ '<input type="radio" name="choose" value="C"/>' +
+ '</div>', function(){
+ this.choose = 'A';
+ });
+
+ expect(scope.choose).toEqual('A');
+ });
+
+ });
+
+ it('should support type="select-one"', function(){
+ compile(
+ '<select name="selection">' +
+ '<option>A</option>' +
+ '<option selected>B</option>' +
+ '</select>');
+ expect(scope.selection).toEqual('B');
+ scope.selection = 'A';
+ scope.$eval();
+ expect(scope.selection).toEqual('A');
+ expect(element[0].childNodes[0].selected).toEqual(true);
+ });
+
+ it('should support type="select-multiple"', function(){
+ compile(
+ '<select name="selection" multiple>' +
+ '<option>A</option>' +
+ '<option selected>B</option>' +
+ '</select>');
+ expect(scope.selection).toEqual(['B']);
+ scope.selection = ['A'];
+ scope.$eval();
+ expect(element[0].childNodes[0].selected).toEqual(true);
+ });
+
+ it('should report error on missing field', function(){
+ compile('<input type="text"/>');
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on assignment error', function(){
+ compile('<input type="text" name="throw \'\'" value="x"/>');
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+
+ it('should report error on ng-change exception', function(){
+ compile('<button ng-change="a-2=x">click</button>');
+ click(element);
+ expect(element.hasClass('ng-exception')).toBeTruthy();
+ });
+ });
+
+ describe('ng:switch', function(){
+ it('should switch on value change', function(){
+ compile('<ng:switch on="select"><div ng-switch-when="1">first:{{name}}</div><div ng-switch-when="2">second:{{name}}</div></ng:switch>');
+ expect(element.html()).toEqual('');
+ scope.select = 1;
+ scope.$eval();
+ expect(element.text()).toEqual('first:');
+ scope.name="shyam";
+ scope.$eval();
+ expect(element.text()).toEqual('first:shyam');
+ scope.select = 2;
+ scope.$eval();
+ expect(element.text()).toEqual('second:shyam');
+ scope.name = 'misko';
+ scope.$eval();
+ expect(element.text()).toEqual('second:misko');
+ });
+
+ it("should match urls", function(){
+ var scope = angular.compile('<ng:switch on="url" using="route:params"><div ng-switch-when="/Book/:name">{{params.name}}</div></ng:switch>');
+ scope.url = '/Book/Moby';
+ scope.$init();
+ expect(scope.$element.text()).toEqual('Moby');
+ });
+
+ it("should match sandwich ids", function(){
+ var scope = {};
+ var match = angular.widget['NG:SWITCH'].route.call(scope, '/a/123/b', '/a/:id');
+ expect(match).toBeFalsy();
+ });
+
+ it('should call init on switch', function(){
+ var scope = angular.compile('<ng:switch on="url" change="name=\'works\'"><div ng-switch-when="a">{{name}}</div></ng:switch>');
+ var cleared = false;
+ scope.url = 'a';
+ scope.$invalidWidgets = {clearOrphans: function(){
+ cleared = true;
+ }};
+ scope.$init();
+ expect(scope.name).toEqual(undefined);
+ expect(scope.$element.text()).toEqual('works');
+ expect(cleared).toEqual(true);
+ });
+ });
+
+ describe('ng:include', function(){
+ it('should include on external file', function() {
+ var element = jqLite('<ng:include src="url" scope="childScope"></ng:include>');
+ var scope = angular.compile(element);
+ scope.childScope = createScope();
+ scope.childScope.name = 'misko';
+ scope.url = 'myUrl';
+ scope.$xhr.cache.data.myUrl = {value:'{{name}}'};
+ scope.$init();
+ expect(element.text()).toEqual('misko');
+ });
+ });
+});
+