Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6a8ad87ee | |||
| 7e6ff401b8 | |||
| 7aeb6a24f7 | |||
| e1ecc34edd | |||
| 29d36e94e1 | |||
| 091c173632 | |||
| c115fa9924 | |||
| b7a7fc7065 | |||
| 21b2a5bd21 | |||
| ca1e45beaf | |||
| 084b83ffa9 | |||
| bf5e5f7bc9 | |||
| 2e9fed7b6c | |||
| ea3228e311 | |||
| 2eb49147d6 | |||
| 13f92de624 | |||
| 2bc39bb0b4 | |||
| 62ae7fccbc | |||
| 3ace81b92a | |||
| 9acf45127e | |||
| 6883e8c7a0 | |||
| 7ae536d053 | |||
| 2170c06924 | |||
| 0e5a24c584 | |||
| 4e8f0d6e9f | |||
| f9b6b61468 | |||
| 555f415290 | |||
| 3800d17703 | |||
| 009059dd1b | |||
| 6b7ddf414d | |||
| 8259f10138 | |||
| ab407de54d | |||
| 0d7fe97aff | |||
| a2a830e227 | |||
| 8336f3f0ba | |||
| e14ac2c3b0 | |||
| a13653c814 | |||
| 8017340cd1 | |||
| 17d1aef66a | |||
| 1856f62cb1 | |||
| 7e1f364177 | |||
| aac68bf2ba | |||
| 4b4292edb8 | |||
| 292d5d1421 | |||
| 66dec77555 | |||
| 8fa79066e2 | |||
| 909415d5ed | |||
| 4421f3d435 | |||
| 22cb600280 | |||
| 5ba227c7cd | |||
| f37f0ea16e | |||
| 91ccb4ba6e | |||
| d0f459c56f | |||
| cbedf55641 | |||
| 988ed451b5 | |||
| fc2f188d4d | |||
| 4b1913c5ec | |||
| 06534413d3 | |||
| e54909f5ef | |||
| 79f2512ba7 | |||
| b0eb831bce | |||
| 7f0b97e02c | |||
| fc7f1ef6a0 |
@@ -10,3 +10,4 @@ performance/temp*.html
|
||||
*~
|
||||
angular.js.tmproj
|
||||
node_modules
|
||||
jsTestDriver*.conf
|
||||
|
||||
+100
-5
@@ -1,11 +1,104 @@
|
||||
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
|
||||
- The Latest Unstable Release: <a href="#0.10.0">0.10.0 chicken-hands</a>
|
||||
- The Latest Unstable Release: <a href="#0.10.2">0.10.2 sneaky-seagull</a>
|
||||
|
||||
<a name="0.10.2"><a/>
|
||||
# 0.10.2 sneaky-seagull (2011-10-08) #
|
||||
|
||||
## Features:
|
||||
|
||||
- jQuery 1.6.4 support (Issue [#556](https://github.com/angular/angular.js/issues/556))
|
||||
- [jqLite](http://docs-next.angularjs.org/api/angular.element) improvements:
|
||||
- Added support for `prop` method
|
||||
([commit](https://github.com/angular/angular.js/commit/3800d177030d20c5c3d04e3601f892c46e723dc2))
|
||||
- Added support for `unbind` method
|
||||
([commit](https://github.com/angular/angular.js/commit/6b7ddf414de82720bbf547b2fa661bf5fcec7bb6))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- Added support for short-circuiting of && and || operators in in angular expressions
|
||||
(Issue [#433](https://github.com/angular/angular.js/issues/433))
|
||||
- Fix for [$limitTo] to properly handle excessive limits (contributed by tehek)
|
||||
(Issue [#571](https://github.com/angular/angular.js/issues/571))
|
||||
- [jqLite]'s css() method now converts dash-separated css property names to camelCase in order to
|
||||
support dash-separated properties on Firefox
|
||||
(Issue [#569](https://github.com/angular/angular.js/issues/569))
|
||||
- action defaults for [$resource]s now take precedence over resource defaults (contributed by
|
||||
Marcello Nuccio)
|
||||
([commit](https://github.com/angular/angular.js/commit/bf5e5f7bc9ebc7dc6cf8fdf3c4923498b22a8654))
|
||||
- Fixed escaping issues in [$route] matcher
|
||||
([commit](https://github.com/angular/angular.js/commit/2bc39bb0b4f81b77597bb52f8572d231cf4f83e2))
|
||||
- Fixed two issues in $browser.defer.cancel mock
|
||||
([commit](https://github.com/angular/angular.js/commit/62ae7fccbc524ff498779564294ed6e1a7a3f51c),
|
||||
[commit](https://github.com/angular/angular.js/commit/8336f3f0ba89b529057027711ab4babd6c2cb649))
|
||||
- Fix for ng:options, which under certain circumstances didn't select the right option element
|
||||
([commit](https://github.com/angular/angular.js/commit/555f4152909e1c0bd5400737a62dc5d63ecd32d3))
|
||||
|
||||
|
||||
## Docs:
|
||||
|
||||
- migrated the docs app to use [$location]'s HTML5 mode (hashbang urls no more)
|
||||
([commit](https://github.com/angular/angular.js/commit/13f92de6246a0af8450fde84b209211a56397fda))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- If Angular is being used with jQuery older than 1.6, some features might not work properly. Please
|
||||
upgrade to jQuery version 1.6.4.
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.10.1"><a/>
|
||||
# 0.10.1 inexorable-juggernaut (2011-09-09) #
|
||||
|
||||
## Features
|
||||
|
||||
- complete rewrite of the $location service with HTML5 support, many API and semantic changes.
|
||||
Please see:
|
||||
- [$location service API docs](http://docs-next.angularjs.org/#!/api/angular.service.$location)
|
||||
- [$location service dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- [location.js source file](https://github.com/angular/angular.js/blob/master/src/service/location.js)
|
||||
- breaking changes section of this changelog
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- $xhr should not covert HTTP status 0 to 200
|
||||
([commit](https://github.com/angular/angular.js/commit/b0eb831bce7d0ea066fd0758124793ed3db6d692))
|
||||
- fixed several doc examples that were broken on IE
|
||||
- ng:change should be called after the new val is set
|
||||
(Issue [#547](https://github.com/angular/angular.js/issues/547))
|
||||
- currency filter should return an empty string for non-numbers
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- $location related changes - for complete list of api changes see:
|
||||
[Migrating from earlier AngularJS releases](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- $location api changes:
|
||||
- $location.href -> $location.absUrl()
|
||||
- $location.hash -> $location.url()
|
||||
- $location.hashPath -> $location.path()
|
||||
- $location.hashSearch -> $location.search()
|
||||
- $location.search -> no equivalent, use $window.location.search (this is so that we can work in
|
||||
hashBang and html5 mode at the same time, check out the docs)
|
||||
- $location.update() / $location.updateHash() -> use $location.url()
|
||||
- n/a -> $location.replace() - new api for replacing history record instead of creating a new one
|
||||
|
||||
- $location semantic changes:
|
||||
- all url pieces are always in sync ($location.path(), $location.url(), $location.search(), ...) -
|
||||
this was previously true only if you used update* methods instead of direct assignment
|
||||
($location.hashPath = 'foo')
|
||||
- we now use (window.history.pushState || onHashChange event || polling) for detecting url changes
|
||||
in the browser (we use the best one available).
|
||||
|
||||
|
||||
|
||||
<a name="0.10.0"><a/>
|
||||
# 0.10.0 chicken-hands (2011-09-02) #
|
||||
|
||||
## Features:
|
||||
## Features
|
||||
|
||||
- complete rewrite of the Scope implementation with several API and semantic changes. Please see:
|
||||
- [angular.scope API docs](http://docs-next.angularjs.org/#!/api/angular.scope)
|
||||
@@ -18,7 +111,7 @@
|
||||
- added [ng:cloak] directive for hiding uncompiled templates
|
||||
|
||||
|
||||
## Bug Fix:
|
||||
## Bug Fixes
|
||||
|
||||
- make [ng:class] friendly towards other code adding/removing classes
|
||||
([commit](https://github.com/angular/angular.js/commit/2a8fe56997fddbad673748ce02abf649a709c4ca))
|
||||
@@ -29,7 +122,7 @@
|
||||
- expose missing [lowercase], [uppercase] and [isDate] APIs.
|
||||
|
||||
|
||||
## Docs:
|
||||
## Docs
|
||||
|
||||
- many (but not all just yet) api docs were proof-read and improved
|
||||
|
||||
@@ -71,7 +164,7 @@
|
||||
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
|
||||
|
||||
|
||||
## Fixes
|
||||
## Bug Fixes
|
||||
- fixed memory leak found in [ng:options] directive
|
||||
([commit](https://github.com/angular/angular.js/commit/6aa04b1db48853340d720e0a1a3e325ac523a06f))
|
||||
- make ng:class-even/odd compatible with ng:class
|
||||
@@ -746,3 +839,5 @@ with the `$route` service
|
||||
[$on]: http://docs-next.angularjs.org/#!/api/angular.scope.$on
|
||||
[$emit]: http://docs-next.angularjs.org/#!/api/angular.scope.$emit
|
||||
[$broadcast]: http://docs-next.angularjs.org/#!/api/angular.scope.$broadcast
|
||||
[$limitTo]: http://docs-next.angularjs.org/api/angular.Array.limitTo
|
||||
[$location]: http://docs-next.angularjs.org/api/angular.service.$location
|
||||
|
||||
@@ -1,61 +1,8 @@
|
||||
require 'yaml'
|
||||
include FileUtils
|
||||
|
||||
ANGULAR = [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/hover.js',
|
||||
'src/service/invalidWidgets.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/service/locale.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/AngularPublic.js',
|
||||
]
|
||||
|
||||
ANGULAR_SCENARIO = [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
]
|
||||
content = File.open('angularFiles.js', 'r') {|f| f.read }
|
||||
files = eval(content.gsub(/angularFiles = /, '').gsub(/:/, '=>'));
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
|
||||
@@ -89,10 +36,10 @@ desc 'Compile Scenario'
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'src/scenario/angular.prefix',
|
||||
ANGULAR,
|
||||
ANGULAR_SCENARIO,
|
||||
files['angularSrc'],
|
||||
files['angularScenario'],
|
||||
'src/scenario/angular.suffix',
|
||||
]
|
||||
|
||||
@@ -193,7 +140,7 @@ task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :gen
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
ANGULAR,
|
||||
files['angularSrc'],
|
||||
'src/angular.suffix',
|
||||
]
|
||||
|
||||
@@ -262,7 +209,8 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
@@ -270,10 +218,46 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
@@ -285,14 +269,16 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
angularFiles = {
|
||||
'angularSrc': [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/hover.js',
|
||||
'src/service/invalidWidgets.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/sniffer.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/service/locale.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/AngularPublic.js',
|
||||
],
|
||||
|
||||
'angularScenario': [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
],
|
||||
|
||||
'jstd': [
|
||||
'lib/jasmine-1.0.1/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_remove.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdExclude': [
|
||||
'test/jquery_alias.js',
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdScenario': [
|
||||
'build/angular-scenario.js',
|
||||
'build/jstd-scenario-adapter-config.js',
|
||||
'build/jstd-scenario-adapter.js',
|
||||
'build/docs/docs-scenario.js'
|
||||
],
|
||||
|
||||
'jstdPerf': [
|
||||
'lib/jasmine-1.0.1/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'angularSrc',
|
||||
'src/angular-mocks.js',
|
||||
'perf/data/*.js',
|
||||
'perf/testUtils.js',
|
||||
'perf/*.js'
|
||||
],
|
||||
|
||||
'jstdPerfExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdJquery': [
|
||||
'lib/jasmine-1.0.1/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_alias.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdJqueryExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'test/jquery_remove.js'
|
||||
]
|
||||
}
|
||||
@@ -90,7 +90,7 @@ master.$equals(form)}}">Save</button>
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[name=form.name]').val()).toEqual('John Smith');
|
||||
expect(element(':input[name="form.name"]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -53,7 +53,7 @@ to retrieve Buzz activity and comments.
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('fetch buzz and expand', function() {
|
||||
xit('fetch buzz and expand', function() {
|
||||
element(':button:contains(fetch)').click();
|
||||
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
|
||||
element('.buzz a:contains(Expand replies):first').click();
|
||||
|
||||
@@ -28,14 +28,14 @@ controller.
|
||||
|
||||
In this example we have a simple app which consist of two screens:
|
||||
|
||||
* Welcome: url `#` Show the user contact information.
|
||||
* Settings: url `#/settings` Show an edit screen for user contact information.
|
||||
* Welcome: url `welcome` Show the user contact information.
|
||||
* Settings: url `settings` Show an edit screen for user contact information.
|
||||
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* {@link ./examples/settings.html}
|
||||
* {@link ./examples/welcome.html}
|
||||
* <a href="./examples/settings.html" ng:ext-link>./examples/settings.html</a>
|
||||
* <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -43,7 +43,7 @@ The two partials are defined in the following URLs:
|
||||
AppCntl.$inject = ['$route']
|
||||
function AppCntl($route) {
|
||||
// define routes
|
||||
$route.when("", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
|
||||
$route.parent(this);
|
||||
|
||||
@@ -61,7 +61,9 @@ The two partials are defined in the following URLs:
|
||||
}
|
||||
};
|
||||
|
||||
function SettingsCntl(){
|
||||
SettingsCntl.$inject = ['$location'];
|
||||
function SettingsCntl($location){
|
||||
this.$location = $location;
|
||||
this.cancel();
|
||||
}
|
||||
SettingsCntl.prototype = {
|
||||
@@ -71,13 +73,13 @@ The two partials are defined in the following URLs:
|
||||
|
||||
save: function(){
|
||||
angular.copy(this.form, this.person);
|
||||
window.location.hash = "#";
|
||||
this.$location.path('/welcome');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="AppCntl">
|
||||
<h1>Your App Chrome</h1>
|
||||
[ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
|
||||
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
|
||||
<hr/>
|
||||
<span style="background-color: blue; color: white; padding: 3px;">
|
||||
Partial: {{$route.current.template}}
|
||||
|
||||
@@ -68,18 +68,18 @@ ng:validate="regexp:zip"/><br/><br/>
|
||||
});
|
||||
|
||||
it('should validate zip', function(){
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
|
||||
.not().toMatch(/ng-validation-error/);
|
||||
using('.example').input('user.address.zip').enter('abc');
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
|
||||
.toMatch(/ng-validation-error/);
|
||||
});
|
||||
|
||||
it('should validate state', function(){
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
|
||||
.not().toMatch(/ng-validation-error/);
|
||||
using('.example').input('user.address.state').enter('XXX');
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
|
||||
.toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
|
||||
@@ -26,7 +26,7 @@ no connection between the controller and the view.
|
||||
'cursor': 'pointer'
|
||||
};
|
||||
this.reset();
|
||||
this.$watch('$location.hashSearch.board', this.readUrl);
|
||||
this.$watch('$location.search().board', this.readUrl);
|
||||
}
|
||||
TicTacToeCntl.prototype = {
|
||||
dropPiece: function(row, col) {
|
||||
@@ -62,7 +62,7 @@ no connection between the controller and the view.
|
||||
angular.forEach(this.board, function(row){
|
||||
rows.push(row.join(','));
|
||||
});
|
||||
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
|
||||
this.$location.search({board: rows.join(';') + '/' + this.nextMove});
|
||||
},
|
||||
readUrl: function(scope, value) {
|
||||
if (value) {
|
||||
@@ -72,8 +72,6 @@ no connection between the controller and the view.
|
||||
this.board[col] = row.split(',');
|
||||
}, this);
|
||||
this.grade();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,634 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Using $location
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the {@link
|
||||
https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
- Exposes the current URL in the browser address bar, so you can
|
||||
- Watch and observe the URL.
|
||||
- Change the URL.
|
||||
- Synchronizes the URL with the browser when the user
|
||||
- Changes the address bar.
|
||||
- Clicks the back or forward button (or clicks a History link).
|
||||
- Clicks on a link.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>window.location</td>
|
||||
<td>$location service</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">purpose</td>
|
||||
<td>allow read/write access to the current browser location</td>
|
||||
<td>same</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">API</td>
|
||||
<td>exposes "raw" object with properties that can be directly modified</td>
|
||||
<td>exposes jQuery-style getters and setters</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">integration with angular application life-cycle</td>
|
||||
<td>none</td>
|
||||
<td>knows about all internal life-cycle phases, integrates with $watch, ...</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">seamless integration with html5 API</td>
|
||||
<td>no</td>
|
||||
<td>yes (with a fallback for legacy browsers)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">aware of docroot/context from which the application is loaded</td>
|
||||
<td>no - window.location.path returns "/docroot/actual/path"</td>
|
||||
<td>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
## What does it not do?
|
||||
Does not cause a full page reload when the browser URL is changed. To reload the page after
|
||||
changing the URL, use the lower-level API, `$window.location.href`.
|
||||
|
||||
|
||||
# General overview of the API
|
||||
|
||||
The `$location` service can behave differently, depending on the configuration that was provided to
|
||||
it when it was instantiated. The default configuration is suitable for many applications, for
|
||||
others customizing the configuration can enable new features.
|
||||
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
|
||||
To configure the `$location` service, you define the `$config` service which is an object with
|
||||
configuration properties:
|
||||
|
||||
- **html5Mode**: {boolean}<br />
|
||||
`true` - see Html5 mode<br />
|
||||
`false` - see Hashbang mode<br />
|
||||
default: `false`
|
||||
|
||||
- **hashPrefix**: {string}<br />
|
||||
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
|
||||
default: `'!'`
|
||||
|
||||
### Example configuration
|
||||
<pre>
|
||||
angular.service('$config', function() {
|
||||
return {
|
||||
html5mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
## Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
<pre>
|
||||
// get the current path
|
||||
$location.path();
|
||||
|
||||
// change the path
|
||||
$location.path('/newValue')
|
||||
</pre>
|
||||
|
||||
All of the setter methods return the same `$location` object to allow chaining. For example, to
|
||||
change multiple segments in one go, chain setters like this:
|
||||
<pre>$location.path('/newValue').search({key: value});</pre>
|
||||
|
||||
All setter methods take an optional boolean flag parameter, which signifies whether current history
|
||||
record should be replaced or if a new record should be created (default). To change the current URL
|
||||
without creating a new browser history record you can call:
|
||||
<pre>$location.path('/newVal', true);</pre>
|
||||
|
||||
Note that the setters don't update `window.location` immediately. Instead, `$location` service is
|
||||
aware of the {@link api/angular.scope scope} life-cycle and coalesces multiple `$location`
|
||||
mutations into one "commit" to the `window.location` object during the scope `$flush` phase. Since
|
||||
any of the setters can take the replace flag, it's enough for one setter to use this flag in order
|
||||
to make the entire "commit" a replace operation rather than addition to the browser history.
|
||||
|
||||
### Setters and character encoding
|
||||
You can pass special characters to `$location` service and it will encode them according to rules
|
||||
specified in {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. When you access the methods:
|
||||
|
||||
- All values that are passed to `$location` setter methods, `path()`, `search()`, `hash()`, are
|
||||
encoded.
|
||||
- Getters (calls to methods without parameters) return decoded values for the following methods
|
||||
`path()`, `search()`, `hash()`.
|
||||
- When you call the `absUrl()` method, the returned value is a full url with its segments encoded.
|
||||
- When you call the `url()` method, the returned value is path, search and hash, in the form
|
||||
`/path?search=a&b=c#hash`. The segments are encoded as well.
|
||||
|
||||
|
||||
# Hashbang and Html5 Modes
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **Html5 mode** which is based on using the
|
||||
Html5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
<img src="img/guide/hashbang_vs_regular_url.jpg">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>Hashbang mode</td>
|
||||
<td>Html5 mode</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">configuration</td>
|
||||
<td>the default</td>
|
||||
<td>{ html5Mode: true }</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">URL format</td>
|
||||
<td>hashbang URLs in all browsers</td>
|
||||
<td>regular URLs in modern browser, hashbang URLs in old browser</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head"><a href=""> link rewriting</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">requires server-side configuration</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$config', function() {
|
||||
return {
|
||||
html5Mode: false,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// open http://host.com/base/index.html#!/a
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/a'
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo')
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
|
||||
</pre>
|
||||
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX Applications
|
||||
Crawlable}.
|
||||
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API, which allows for use of regular URL path and search segments,
|
||||
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
|
||||
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
|
||||
having to worry about whether the browser displaying your app supports the history API or not; the
|
||||
`$location` service transparently uses the best available option.
|
||||
|
||||
- Opening a regular URL in a legacy browser -> redirects to a hashbang URL
|
||||
- Opening hashbang URL in a modern browser -> rewrites to a regular URL
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$config', function() {
|
||||
return {
|
||||
html5Mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// in browser with html5 history support:
|
||||
// open http://host.com/#!/a -> rewrite to http://host.com/a
|
||||
// (replacing the http://host.com/#!/a history record)
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo');
|
||||
$location.absUrl() == 'http://host.com/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.url() == 'new?x=y'
|
||||
$location.absUrl() == 'http://host.com/new?x=y'
|
||||
|
||||
// in browser without html5 history support:
|
||||
// open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
|
||||
// (again replacing the http://host.com/new?x=y history item)
|
||||
$location.path() == '/new'
|
||||
$location.search() == {x: 'y'}
|
||||
|
||||
$location.path('/foo/bar');
|
||||
$location.path() == '/foo/bar'
|
||||
$location.url() == '/foo/bar?x=y'
|
||||
$location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
|
||||
</pre>
|
||||
|
||||
### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### Html link rewriting
|
||||
|
||||
When you use the history API mode, you will need different links in different browser, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
- In a legacy browser, the URL changes to `/index.html#!/some?foo=bar`
|
||||
- In a modern browser, the URL changes to `/some?foo=bar`
|
||||
|
||||
|
||||
In cases like the following, links are not rewritten; instead, the browser will perform a full page
|
||||
reload to the original link.
|
||||
|
||||
- Links with an `ng:ext-link` directive<br />
|
||||
Example: `<a href="/ext/link?a=b" ng:ext-link>link</a>`
|
||||
- Links that contain `target="_blank"`<br />
|
||||
Example: `<a href="/ext/link?a=b" target="_blank">link</a>`
|
||||
- Absolute links that go to a different domain<br />
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
|
||||
### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html)
|
||||
|
||||
### Crawling your app
|
||||
|
||||
If you want your AJAX application to be indexed by web crawlers, you rill need to add the following
|
||||
meta tag to the HEAD section of your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
This statement causes a crawler to request links with empty `_escaped_fragment_` parameter so that
|
||||
your server can recognize the crawler and serve it HTML snapshots. For more information about this
|
||||
technique, see {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX
|
||||
Applications Crawlable}.
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. You must use an absolute path because the
|
||||
path is going to be rewritten. You can use `<base href="" />` tag as well.
|
||||
|
||||
Running Angular apps with the History API enabled from document root is strongly encouraged as it
|
||||
takes care of all relative link issues. **Otherwise you have to specify <base href="" /> !**
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
Because of rewriting capability in Html5 mode, your users will be able to open regular url links in
|
||||
legacy browsers and hashbang links in modern browser:
|
||||
|
||||
- Modern browser will rewrite hashbang URLs to regular URLs.
|
||||
- Older browsers will redirect regular URLs to hashbang URLs.
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In this examples we use `<base href="/base/index.html" />`
|
||||
|
||||
<ul class="doc-example">
|
||||
<li ng:non-bindable class="html5-hashbang-example">
|
||||
<div id="html5-mode" ng:controller="Html5Cntl">
|
||||
<h3>Browser with History API</h3>
|
||||
<ng:address-bar browser="html5"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
|
||||
<div id="hashbang-mode" ng:controller="HashbangCntl">
|
||||
<h3>Browser without History API</h3>
|
||||
<ng:address-bar browser="hashbang"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
function FakeBrowser(initUrl, baseHref) {
|
||||
this.onUrlChange = function(fn) {
|
||||
this.urlChange = fn;
|
||||
};
|
||||
|
||||
this.url = function() {
|
||||
return initUrl;
|
||||
};
|
||||
|
||||
this.defer = function(fn, delay) {
|
||||
setTimeout(function() { fn(); }, delay || 0);
|
||||
};
|
||||
|
||||
this.baseHref = function() {
|
||||
return baseHref;
|
||||
};
|
||||
|
||||
this.hover = angular.noop;
|
||||
this.notifyWhenOutstandingRequests = angular.noop;
|
||||
}
|
||||
|
||||
var browsers = {
|
||||
html5: new FakeBrowser('http://www.host.com/base/path?a=b#h', '/base/index.html'),
|
||||
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h',
|
||||
'/base/index.html')
|
||||
};
|
||||
|
||||
function Html5Cntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
function HashbangCntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
angular.widget('ng:address-bar', function(tpl) {
|
||||
return function(elm) {
|
||||
var browser = browsers[elm.attr('browser')],
|
||||
input = angular.element('<input type="text" />').val(browser.url()),
|
||||
delay;
|
||||
|
||||
input.bind('keypress keyup keydown', function() {
|
||||
if (!delay) {
|
||||
delay = setTimeout(fireUrlChange, 250);
|
||||
}
|
||||
});
|
||||
|
||||
browser.url = function(url) {
|
||||
return input.val(url);
|
||||
};
|
||||
|
||||
elm.append('Address: ').append(input);
|
||||
|
||||
function fireUrlChange() {
|
||||
delay = null;
|
||||
browser.urlChange(input.val());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function initEnv(name) {
|
||||
var root = angular.element(document.getElementById(name + '-mode'));
|
||||
var scope = angular.scope(null, {
|
||||
$config: {html5Mode: true, hashPrefix: '!'},
|
||||
$browser: browsers[name],
|
||||
$document: root,
|
||||
$sniffer: {history: name == 'html5'}
|
||||
});
|
||||
|
||||
angular.compile(root)(scope).$apply();
|
||||
root.bind('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
initEnv('html5');
|
||||
initEnv('hashbang');
|
||||
</script>
|
||||
|
||||
|
||||
# Caveats
|
||||
|
||||
## Page reload navigation
|
||||
|
||||
The `$location` service allows you to change only the URL; it does not allow you to reload the
|
||||
page. When you need to change the URL and reload the page or navigate to a different page, please
|
||||
use a lower level API, {@link api/angular.service.$window $window.location.href}.
|
||||
|
||||
## Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about Angular's {@link api/angular.scope scope} life-cycle. When a URL changes in
|
||||
the browser it updates the `$location` and calls `$apply` so that all $watchers / $observers are
|
||||
notified.
|
||||
When you change the `$location` inside the `$digest` phase everything is ok; `$location` will
|
||||
propagate this change into browser and will notify all the $watchers / $observers.
|
||||
When you want to change the `$location` from outside Angular (for example, through a DOM Event or
|
||||
during testing) - you must call `$apply` to propagate the changes.
|
||||
|
||||
## $location.path() and ! or / prefixes
|
||||
|
||||
A path should always begin with forward slash (`/`); the `$location.path()` setter will add the
|
||||
forward slash if it is missing.
|
||||
|
||||
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
|
||||
hashPrefix.
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
api/angular.scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
|
||||
<pre>
|
||||
angular.service('$serviceUnderTest', function($location) {
|
||||
// whatever it does...
|
||||
};
|
||||
|
||||
describe('$serviceUnderTest', function() {
|
||||
var scope, $location, $sut;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$location = scope.$service('$location');
|
||||
$sut = scope.$service('$serviceUnderTest');
|
||||
});
|
||||
|
||||
it('should...', function() {
|
||||
$location.path('/new/path');
|
||||
scope.$apply();
|
||||
|
||||
// test whatever the service should do...
|
||||
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
In earlier releases of Angular, `$location` used `hashPath` or `hashSearch` to process path and
|
||||
search methods. With this release, the `$location` service processes path and search methods and
|
||||
then uses the information it obtains to compose hashbang URLs (such as
|
||||
`http://server.com/#!/path?search=a`), when necessary.
|
||||
|
||||
## Changes to your code
|
||||
|
||||
<table>
|
||||
<tr class="head">
|
||||
<td>Navigation inside the app</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br
|
||||
/>$location.updateHash(value)</td>
|
||||
<td>$location.path(path).search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath = path</td>
|
||||
<td>$location.path(path)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch = search</td>
|
||||
<td>$location.search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.update(value)</td>
|
||||
<td>$window.location.href = value</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location[protocol | host | port | path | search]</td>
|
||||
<td>$window.location[protocol | host | port | path | search]</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath</td>
|
||||
<td>$location.path()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch</td>
|
||||
<td>$location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br
|
||||
/>$location.hash</td>
|
||||
<td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br
|
||||
/>$location.path() + $location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.path<br />$location.search</td>
|
||||
<td>$window.location.path<br />$window.location.search</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Two-way binding to $location
|
||||
|
||||
The Angular's compiler currently does not support two-way binding for methods (see {@link
|
||||
https://github.com/angular/angular.js/issues/404 issue}). If you should require two-way binding,
|
||||
you will need to specify an extra property that has two watchers. For example:
|
||||
<pre>
|
||||
<!-- html -->
|
||||
<input type="text" name="locationPath" />
|
||||
</pre>
|
||||
<pre>
|
||||
// js - controller
|
||||
this.$watch('locationPath', function(scope, path) {
|
||||
$location.path(path);
|
||||
});
|
||||
|
||||
this.$watch('$location.path()', function(scope, path) {
|
||||
scope.locationPath = path;
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link api/angular.service.$location $location API}
|
||||
|
||||
|
||||
|
||||
-1
@@ -47,6 +47,5 @@ as you normally would:
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
* {@link dev_guide.templates.css Working With CSS in Angular}
|
||||
* {@link dev_guide.templates.formatters Angular Formatters}
|
||||
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
|
||||
@@ -136,7 +136,7 @@ In addition, angular comes with a set of Services, which have the following prop
|
||||
# Where To Go Next
|
||||
|
||||
* If you like what you've learned so far, you should definitely check out our awesome {@link
|
||||
tutorial/ Tutorial}, which walk you through the process of building real apps with AngularJS.
|
||||
tutorial/ Tutorial}, which walks you through the process of building real apps with AngularJS.
|
||||
|
||||
* For further explanations and examples of the AngularJS concepts presented on this page, see the
|
||||
{@link guide/index Developer Guide}.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -327,8 +327,8 @@ describe('ngdoc', function(){
|
||||
expect(doc.requires).toEqual([
|
||||
{name:'$service', text:'<p>for \n<code>A</code></p>'},
|
||||
{name:'$another', text:'<p>for <code>B</code></p>'}]);
|
||||
expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>');
|
||||
expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>');
|
||||
expect(doc.html()).toContain('<a href="api/angular.service.$service">$service</a>');
|
||||
expect(doc.html()).toContain('<a href="api/angular.service.$another">$another</a>');
|
||||
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
|
||||
expect(doc.html()).toContain('<p>for <code>B</code></p>');
|
||||
});
|
||||
@@ -429,13 +429,13 @@ describe('ngdoc', function(){
|
||||
doc.parse();
|
||||
|
||||
expect(doc.description).
|
||||
toContain('foo <a href="#!/api/angular.foo"><code>angular.foo</code></a>');
|
||||
toContain('foo <a href="api/angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('da <a href="#!/api/angular.foo"><code>bar foo bar</code></a>');
|
||||
toContain('da <a href="api/angular.foo"><code>bar foo bar</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('dad<a href="#!/api/angular.foo"><code>angular.foo</code></a>');
|
||||
toContain('dad<a href="api/angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!/api/angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
toContain('<a href="api/angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
|
||||
expect(doc.description).
|
||||
@@ -447,7 +447,7 @@ describe('ngdoc', function(){
|
||||
'{@link\napi/angular.foo\na\nb}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!/api/angular.foo"><code>a b</code></a>');
|
||||
toContain('<a href="api/angular.foo"><code>a b</code></a>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ function appCache(path) {
|
||||
|
||||
var resultPostfix = ["",
|
||||
"FALLBACK:",
|
||||
"/offline.html",
|
||||
"/ /build/docs/index.html",
|
||||
"",
|
||||
"# allow access to google analytics and twitter when we are online",
|
||||
"NETWORK:",
|
||||
@@ -68,7 +68,7 @@ function appCacheTemplate() {
|
||||
"img/yellow_bkgnd.jpg",
|
||||
"",
|
||||
"FALLBACK:",
|
||||
"/ offline.html",
|
||||
"/ /build/docs/offline.html",
|
||||
"",
|
||||
"# allow access to google analytics and twitter when we are online",
|
||||
"NETWORK:",
|
||||
|
||||
+15
-16
@@ -22,7 +22,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
|
||||
ngdoc.merge(docs);
|
||||
var fileFutures = [];
|
||||
docs.forEach(function(doc){
|
||||
fileFutures.push(writer.output(doc.section + '/' + doc.id + '.html', doc.html()));
|
||||
fileFutures.push(writer.output('partials/' + doc.section + '/' + doc.id + '.html', doc.html()));
|
||||
});
|
||||
|
||||
writeTheRest(fileFutures);
|
||||
@@ -30,7 +30,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
|
||||
return Q.deep(fileFutures);
|
||||
}).then(function generateManifestFile() {
|
||||
return appCache('build/docs/').then(function(list) {
|
||||
writer.output('appcache-offline.manifest',list)
|
||||
writer.output('appcache-offline.manifest', list);
|
||||
});
|
||||
}).then(function printStats() {
|
||||
console.log('DONE. Generated ' + docs.length + ' pages in ' + (now()-start) + 'ms.' );
|
||||
@@ -43,28 +43,27 @@ function writeTheRest(writesFuture) {
|
||||
writesFuture.push(writer.copyDir('img'));
|
||||
writesFuture.push(writer.copyDir('examples'));
|
||||
|
||||
var manifest = 'manifest="appcache.manifest"',
|
||||
jq = '<script src="jquery.min.js"></script>',
|
||||
ngMin = '<script src="../angular.min.js" ng:autobind></script>',
|
||||
ng = '<script src="../angular.js" ng:autobind></script>'
|
||||
var manifest = 'manifest="/build/docs/appcache.manifest"';
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
|
||||
writer.replace, {'doc:manifest': manifest,
|
||||
'<!-- angular script place holder -->': ngMin}));
|
||||
writer.replace, {'doc:manifest': manifest}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
|
||||
writer.replace, {'doc:manifest': manifest,
|
||||
'<!-- angular script place holder -->': ngMin,
|
||||
'<!-- jquery place holder -->': jq}));
|
||||
writer.replace, {'doc:manifest': manifest}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-nocache.html',
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html',
|
||||
writer.replace, {'doc:manifest': '',
|
||||
'<!-- angular script place holder -->': ng}));
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html',
|
||||
writer.replace, {'doc:manifest': '',
|
||||
'<!-- angular script place holder -->': ng,
|
||||
'<!-- jquery place holder -->': jq}));
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
writesFuture.push(writer.copyTpl('offline.html'));
|
||||
writesFuture.push(writer.copyTpl('docs-scenario.html'));
|
||||
|
||||
+6
-6
@@ -133,7 +133,7 @@ Doc.prototype = {
|
||||
|
||||
if (!isFullUrl) self.links.push(absUrl);
|
||||
|
||||
return '<a href="' + (isFullUrl ? '' + url : '#!/' + absUrl) + '">'
|
||||
return '<a href="' + absUrl + '">'
|
||||
+ (isAngular ? '<code>' : '')
|
||||
+ (title || url).replace(/\n/g, ' ')
|
||||
+ (isAngular ? '</code>' : '')
|
||||
@@ -243,7 +243,7 @@ Doc.prototype = {
|
||||
}
|
||||
dom.h('Dependencies', self.requires, function(require){
|
||||
dom.tag('code', function(){
|
||||
dom.tag('a', {href:"#!/api/angular.service." + require.name}, require.name);
|
||||
dom.tag('a', {href: 'api/angular.service.' + require.name}, require.name);
|
||||
});
|
||||
dom.html(require.text);
|
||||
});
|
||||
@@ -570,23 +570,23 @@ function scenarios(docs){
|
||||
var specs = [];
|
||||
|
||||
specs.push('describe("angular+jqlite", function() {');
|
||||
appendSpecs('index.html');
|
||||
appendSpecs('index-nocache.html#!/');
|
||||
specs.push('});');
|
||||
|
||||
specs.push('');
|
||||
specs.push('');
|
||||
|
||||
specs.push('describe("angular+jquery", function() {');
|
||||
appendSpecs('index-jq.html');
|
||||
appendSpecs('index-jq-nocache.html#!/');
|
||||
specs.push('});');
|
||||
|
||||
return specs.join('\n');
|
||||
|
||||
function appendSpecs(htmlFile) {
|
||||
function appendSpecs(urlPrefix) {
|
||||
docs.forEach(function(doc){
|
||||
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
|
||||
specs.push(' beforeEach(function(){');
|
||||
specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");');
|
||||
specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
|
||||
specs.push(' });');
|
||||
specs.push(' ');
|
||||
doc.scenarios.forEach(function(scenario){
|
||||
|
||||
@@ -8,4 +8,8 @@
|
||||
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
|
||||
RewriteRule appcache.manifest appcache-offline.manifest
|
||||
RewriteRule appcache.manifest appcache-offline.manifest
|
||||
|
||||
|
||||
## HTML5 URL Support ##
|
||||
RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html
|
||||
|
||||
@@ -56,8 +56,13 @@
|
||||
element.append(tabs);
|
||||
|
||||
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
|
||||
|
||||
try {
|
||||
window.eval(script);
|
||||
if (window.execScript) { // IE
|
||||
window.execScript(script || '"stupid IE!"'); // IE complains when evaling empty string
|
||||
} else {
|
||||
window.eval(script);
|
||||
}
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
|
||||
@@ -425,3 +425,37 @@ li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px;
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
td.head, thead td, .head td {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
td.empty-corner-lt {
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* service.$location Html5 mode example */
|
||||
.html5-hashbang-example {
|
||||
height: 255px;
|
||||
margin-left: -40px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.html5-hashbang-example div {
|
||||
float: left;
|
||||
width: 375px;
|
||||
}
|
||||
|
||||
.html5-hashbang-example div input {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
+20
-11
@@ -4,7 +4,8 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
|
||||
var self = this,
|
||||
OFFLINE_COOKIE_NAME = 'ng-offline',
|
||||
HAS_HASH = /#/;
|
||||
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
|
||||
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
|
||||
this.$location = $location;
|
||||
|
||||
@@ -13,13 +14,14 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
self.subpage = false;
|
||||
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
|
||||
|
||||
if (!HAS_HASH.test($location.href)) {
|
||||
$location.hashPath = '!/api';
|
||||
if (!$location.path() || INDEX_PATH.test($location.path())) {
|
||||
$location.path('/api').replace();
|
||||
}
|
||||
|
||||
this.$watch('$location.hashPath', function(scope, hashPath) {
|
||||
if (hashPath.match(/^!/)) {
|
||||
var parts = hashPath.substring(1).split('/');
|
||||
this.$watch('$location.path()', function(scope, path) {
|
||||
// ignore non-doc links which are used in examples
|
||||
if (DOCS_PATH.test(path)) {
|
||||
var parts = path.split('/');
|
||||
self.sectionId = parts[1];
|
||||
self.partialId = parts[2] || 'index';
|
||||
self.pages = angular.Array.filter(NG_PAGES, {section:self.sectionId});
|
||||
@@ -39,11 +41,11 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
});
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!/' + page.section + '/' + page.id;
|
||||
return page.section + '/' + page.id;
|
||||
};
|
||||
|
||||
this.getCurrentPartial = function(){
|
||||
return this.partialId ? ('./' + this.sectionId + '/' + this.partialId + '.html') : '';
|
||||
return this.partialId ? ('./partials/' + this.sectionId + '/' + this.partialId + '.html') : '';
|
||||
};
|
||||
|
||||
this.getClass = function(page) {
|
||||
@@ -67,13 +69,13 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
this.afterPartialLoaded = function() {
|
||||
SyntaxHighlighter.highlight();
|
||||
$window.scrollTo(0,0);
|
||||
$window._gaq.push(['_trackPageview', $location.hashPath.substr(1)]);
|
||||
$window._gaq.push(['_trackPageview', $location.path()]);
|
||||
};
|
||||
|
||||
this.getFeedbackUrl = function() {
|
||||
return "mailto:angular@googlegroups.com?" +
|
||||
"subject=" + escape("Feedback on " + $location.href) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
|
||||
"subject=" + escape("Feedback on " + $location.absUrl()) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.absUrl() + " and wanted to ask ....");
|
||||
};
|
||||
|
||||
/** stores a cookie that is used by apache to decide which manifest ot send */
|
||||
@@ -123,3 +125,10 @@ function TutorialInstructionsCtrl($cookieStore) {
|
||||
$cookieStore.put('selEnv', id);
|
||||
};
|
||||
}
|
||||
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,12 +5,66 @@
|
||||
doc:manifest>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
|
||||
<meta name="fragment" content="!">
|
||||
<link rel="stylesheet" href="docs-combined.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/syntaxhighlighter-combined.css" type="text/css"/>
|
||||
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
|
||||
<script type="text/javascript">
|
||||
// dynamically add base tag as well as css and javascript files.
|
||||
// we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources
|
||||
// before the base attribute is added, causing 404 and terribly slow loading of the docs app.
|
||||
(function() {
|
||||
var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', 'index.html'])[1],
|
||||
rUrl = /(api|guide|misc|tutorial|cookbook|index[^\.]*\.html).*$/,
|
||||
baseUrl = location.href.replace(rUrl, indexFile),
|
||||
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
|
||||
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
|
||||
angularPath = debug ? '../angular.js' : '../angular.min.js',
|
||||
headEl = document.getElementsByTagName('head')[0],
|
||||
sync = true;
|
||||
|
||||
addTag('base', {href: baseUrl});
|
||||
addTag('link', {rel: 'stylesheet', href: 'docs-combined.css', type: 'text/css'});
|
||||
addTag('link', {rel: 'stylesheet', href: 'syntaxhighlighter/syntaxhighlighter-combined.css',
|
||||
type: 'text/css'});
|
||||
addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
|
||||
if (jQuery) addTag('script', {src: 'jquery.min.js'});
|
||||
addTag('script', {src: angularPath, 'ng:autobind':''}, sync);
|
||||
addTag('script', {src: 'docs-combined.js'}, sync);
|
||||
addTag('script', {src: 'docs-keywords.js'}, sync);
|
||||
|
||||
function addTag(name, attributes, sync) {
|
||||
var el = document.createElement(name),
|
||||
attrName;
|
||||
|
||||
for (attrName in attributes) {
|
||||
el.setAttribute(attrName, attributes[attrName]);
|
||||
}
|
||||
|
||||
sync ? document.write(outerHTML(el)) : headEl.appendChild(el);
|
||||
}
|
||||
|
||||
function outerHTML(node){
|
||||
// if IE, Chrome take the internal method otherwise build one
|
||||
return node.outerHTML || (
|
||||
function(n){
|
||||
var div = document.createElement('div'), h;
|
||||
div.appendChild(n);
|
||||
h = div.innerHTML;
|
||||
div = null;
|
||||
return h;
|
||||
})(node);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
<script>
|
||||
// GA asynchronous tracker
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8594346-3']);
|
||||
@@ -21,14 +75,6 @@
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -45,11 +91,11 @@
|
||||
|
||||
<ul id="navbar">
|
||||
<li><a href="http://angularjs.org/">AngularJS</a></li>
|
||||
<li><a href="#!/misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
|
||||
<li><a href="#!/tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
|
||||
<li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li>
|
||||
<li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
|
||||
<li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
|
||||
<li><a href="misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
|
||||
<li><a href="tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
|
||||
<li><a href="api" ng:class="selectedSection('api')">API Reference</a></li>
|
||||
<li><a href="cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
|
||||
<li><a href="guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="sidebar">
|
||||
@@ -100,11 +146,5 @@
|
||||
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="syntaxhighlighter/syntaxhighlighter-combined.js"></script>
|
||||
<!-- jquery place holder -->
|
||||
<!-- angular script place holder -->
|
||||
<script src="docs-combined.js"></script>
|
||||
<script src="docs-keywords.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
../../../lib/jquery/jquery-1.4.2.min.js
|
||||
../../../lib/jquery/jquery.min.js
|
||||
+2
-2
@@ -8,14 +8,14 @@ var Q = require('qq');
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
var fs = require('fs');
|
||||
|
||||
exports.output = function(file, content){
|
||||
exports.output = function(file, content) {
|
||||
console.log('writing ', file);
|
||||
var fullPath = OUTPUT_DIR + file;
|
||||
var dir = parent(fullPath);
|
||||
return Q.when(exports.makeDir(dir), function(error) {
|
||||
qfs.write(fullPath,exports.toString(content));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//recursively create directory
|
||||
exports.makeDir = function (path) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!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/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!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/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="../angular-minified.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!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/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript" src="../angular-debug.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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="../../lib/jquery/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="tweeterclient.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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="../../lib/jquery/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="tweeterclient.js"></script>
|
||||
</head>
|
||||
|
||||
@@ -5,21 +5,26 @@ var https = require('https');
|
||||
var fs = require('fs');
|
||||
|
||||
var collections = {
|
||||
'guide': 'http://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmANGUwMGVhZmYtMTk1ZC00NTdmLWIxMDAtZGI5YWNlZjQ2YjZl/contents',
|
||||
'api': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDYjMwYTc2YWUtZTgzYy00YjIxLThlZDYtYWJlOTFlNzE2NzEw/contents',
|
||||
'tutorial': 'http://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmAYWMxYWE3MzYtYzdjYS00OGQxLWJhZjItYzZkMzJiZTRhZjFl/contents',
|
||||
'cookbook': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDNzkxZWM5ZTItN2M5NC00NWIxLTg2ZDMtMmYwNDY1NWM1MGU4/contents',
|
||||
'misc': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDZjVlNmZkYzQtMjZlOC00NmZhLWI5MjAtMGRjZjlkOGJkMDBi/contents'
|
||||
'guide': 'https://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmANGUwMGVhZmYtMTk1ZC00NTdmLWIxMDAtZGI5YWNlZjQ2YjZl/contents',
|
||||
'api': 'https://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDYjMwYTc2YWUtZTgzYy00YjIxLThlZDYtYWJlOTFlNzE2NzEw/contents',
|
||||
'tutorial': 'https://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmAYWMxYWE3MzYtYzdjYS00OGQxLWJhZjItYzZkMzJiZTRhZjFl/contents',
|
||||
'cookbook': 'https://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDNzkxZWM5ZTItN2M5NC00NWIxLTg2ZDMtMmYwNDY1NWM1MGU4/contents',
|
||||
'misc': 'https://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDZjVlNmZkYzQtMjZlOC00NmZhLWI5MjAtMGRjZjlkOGJkMDBi/contents'
|
||||
};
|
||||
|
||||
console.log('Google Docs...');
|
||||
|
||||
var flag = process && process.argv[2];
|
||||
if (flag == '--login')
|
||||
askPassword(function(password){
|
||||
login(process.argv[3], password);
|
||||
});
|
||||
else if (flag == '--fetch') {
|
||||
if (flag == '--login') {
|
||||
var username = process.argv[3];
|
||||
if (username) {
|
||||
askPassword(function(password){
|
||||
login(username, password);
|
||||
});
|
||||
} else {
|
||||
console.log('Missing username!');
|
||||
}
|
||||
} else if (flag == '--fetch') {
|
||||
var collection = process.argv[3];
|
||||
if (collection) {
|
||||
fetch(collection, collections[collection]);
|
||||
@@ -27,8 +32,9 @@ else if (flag == '--fetch') {
|
||||
for (collection in collections)
|
||||
fetch(collection, collections[collection]);
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
help();
|
||||
}
|
||||
|
||||
function help(){
|
||||
console.log('Synopsys');
|
||||
@@ -147,34 +153,36 @@ function getAuthToken(){
|
||||
|
||||
function request(method, url, options, response) {
|
||||
var url = url.match(/http(s?):\/\/(.+?)(\/.*)/);
|
||||
var request = (url[1] ? https : http).request({
|
||||
var isHttps = url[1];
|
||||
var request = (isHttps ? https : http).request({
|
||||
host: url[2],
|
||||
port: (url[1] ? 443 : 80),
|
||||
path: url[3],
|
||||
method: method
|
||||
}, function(res){
|
||||
switch (res.statusCode) {
|
||||
case 200: {
|
||||
case 200:
|
||||
var data = [];
|
||||
res.setEncoding('utf8');
|
||||
res.on('end', function(){
|
||||
response(data.join(''));
|
||||
});
|
||||
res.on('data', function (chunk) {
|
||||
data.push(chunk);
|
||||
});
|
||||
res.on('error', function(e){
|
||||
console.log(e);
|
||||
});
|
||||
res.on('end', function (){ response(data.join('')); });
|
||||
res.on('close', function (){ response(data.join('')); }); // https
|
||||
res.on('data', function (chunk) { data.push(chunk); });
|
||||
res.on('error', function (e){ console.log(e); });
|
||||
break;
|
||||
}
|
||||
case 401: {
|
||||
case 401:
|
||||
console.log('Eror: Login credentials expired! Please login.');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(res);
|
||||
}
|
||||
default:
|
||||
var data = [];
|
||||
console.log('ERROR: ', res.statusCode);
|
||||
console.log('REQUEST URL: ', url[0]);
|
||||
console.log('REQUEST POST: ', options.data);
|
||||
console.log('REQUEST HEADERS: ', options.headers);
|
||||
console.log('RESPONSE HEADERS: ', res.headers);
|
||||
res.on('end', function (){ console.log('BODY: ', data.join('')); });
|
||||
res.on('close', function (){ console.log('BODY: ', data.join('')); }); // https
|
||||
res.on('data', function (chunk) { data.push(chunk); });
|
||||
res.on('error', function (e){ console.log(e); });
|
||||
}
|
||||
});
|
||||
for(var header in options.headers) {
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
/* This file reads in list of files from angularFiles.js and generate various jstd config files */
|
||||
|
||||
var fs = require('fs'),
|
||||
angularSrc,
|
||||
angularScenario;
|
||||
|
||||
fs.readFile('angularFiles.js', function(err, data) {
|
||||
eval(data.toString());
|
||||
var prefix = 'server: http://localhost:9876\n\n',
|
||||
prefixScenario = 'server: http://localhost:9877\n\n';
|
||||
|
||||
angularSrc = angularFiles.angularSrc.join('\n- ');
|
||||
angularScenario = angularFiles.angularScenario.join('\n- ');
|
||||
|
||||
fs.writeFile('./jsTestDriver.conf', prefix + combine(angularFiles.jstd,
|
||||
angularFiles.jstdExclude));
|
||||
|
||||
fs.writeFile('./jsTestDriver-scenario.conf', prefixScenario +
|
||||
combine(angularFiles.jstdScenario) +
|
||||
'\n\nproxy:\n- {matcher: "*", server: "http://localhost:8000"}');
|
||||
|
||||
fs.writeFile('./jsTestDriver-perf.conf', prefix + combine(angularFiles.jstdPerf,
|
||||
angularFiles.jstdPerfExclude));
|
||||
|
||||
fs.writeFile('./jsTestDriver-jquery.conf', prefix + combine(angularFiles.jstdJquery,
|
||||
angularFiles.jstdJqueryExclude));
|
||||
|
||||
fs.writeFile('./jsTestDriver-coverage.conf', prefix +
|
||||
combine(angularFiles.jstd, angularFiles.jstdExclude) +
|
||||
'\n\nplugin:\n- name: "coverage"\n' +
|
||||
'jar: "lib/jstestdriver/coverage.jar"\n' +
|
||||
'module: "com.google.jstestdriver.coverage.CoverageModule"');
|
||||
});
|
||||
|
||||
function combine(load, exclude) {
|
||||
var fileList = 'load:\n- ' + load.join('\n- ');
|
||||
if (exclude) fileList += ('\n\nexclude:\n- ' + exclude.join('\n- '));
|
||||
|
||||
//Replace placeholders for src list before returning
|
||||
return fileList.replace(/@angularSrc/g, angularSrc);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/Compiler.js
|
||||
- src/Scope.js
|
||||
- src/Injector.js
|
||||
- src/parser.js
|
||||
- src/Resource.js
|
||||
- src/Browser.js
|
||||
- src/sanitizer.js
|
||||
- src/jqLite.js
|
||||
- src/apis.js
|
||||
- src/filters.js
|
||||
- src/formatters.js
|
||||
- src/validators.js
|
||||
- src/service/cookieStore.js
|
||||
- src/service/cookies.js
|
||||
- src/service/defer.js
|
||||
- src/service/document.js
|
||||
- src/service/exceptionHandler.js
|
||||
- src/service/hover.js
|
||||
- src/service/invalidWidgets.js
|
||||
- src/service/location.js
|
||||
- src/service/log.js
|
||||
- src/service/resource.js
|
||||
- src/service/route.js
|
||||
- src/service/window.js
|
||||
- src/service/xhr.bulk.js
|
||||
- src/service/xhr.cache.js
|
||||
- src/service/xhr.error.js
|
||||
- src/service/xhr.js
|
||||
- src/service/locale.js
|
||||
- src/directives.js
|
||||
- src/markups.js
|
||||
- src/widgets.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/jstd-scenario-adapter/*.js
|
||||
- src/scenario/*.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/jstd-scenario-adapter/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- test/jquery_alias.js
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
|
||||
plugin:
|
||||
- name: "coverage"
|
||||
jar: "lib/jstestdriver/coverage.jar"
|
||||
module: "com.google.jstestdriver.coverage.CoverageModule"
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_alias.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/Compiler.js
|
||||
- src/Scope.js
|
||||
- src/Injector.js
|
||||
- src/parser.js
|
||||
- src/Resource.js
|
||||
- src/Browser.js
|
||||
- src/sanitizer.js
|
||||
- src/jqLite.js
|
||||
- src/apis.js
|
||||
- src/filters.js
|
||||
- src/formatters.js
|
||||
- src/validators.js
|
||||
- src/service/cookieStore.js
|
||||
- src/service/cookies.js
|
||||
- src/service/defer.js
|
||||
- src/service/document.js
|
||||
- src/service/exceptionHandler.js
|
||||
- src/service/hover.js
|
||||
- src/service/invalidWidgets.js
|
||||
- src/service/location.js
|
||||
- src/service/log.js
|
||||
- src/service/resource.js
|
||||
- src/service/route.js
|
||||
- src/service/routeParams.js
|
||||
- src/service/window.js
|
||||
- src/service/xhr.bulk.js
|
||||
- src/service/xhr.cache.js
|
||||
- src/service/xhr.error.js
|
||||
- src/service/xhr.js
|
||||
- src/service/locale.js
|
||||
- src/directives.js
|
||||
- src/markups.js
|
||||
- src/widgets.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/jstd-scenario-adapter/*.js
|
||||
- src/scenario/*.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/jstd-scenario-adapter/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- src/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- test/jquery_remove.js
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/Compiler.js
|
||||
- src/Scope.js
|
||||
- src/Injector.js
|
||||
- src/parser.js
|
||||
- src/Resource.js
|
||||
- src/Browser.js
|
||||
- src/sanitizer.js
|
||||
- src/jqLite.js
|
||||
- src/apis.js
|
||||
- src/filters.js
|
||||
- src/formatters.js
|
||||
- src/validators.js
|
||||
- src/service/cookieStore.js
|
||||
- src/service/cookies.js
|
||||
- src/service/defer.js
|
||||
- src/service/document.js
|
||||
- src/service/exceptionHandler.js
|
||||
- src/service/hover.js
|
||||
- src/service/invalidWidgets.js
|
||||
- src/service/location.js
|
||||
- src/service/log.js
|
||||
- src/service/resource.js
|
||||
- src/service/route.js
|
||||
- src/service/routeParams.js
|
||||
- src/service/updateView.js
|
||||
- src/service/window.js
|
||||
- src/service/xhr.bulk.js
|
||||
- src/service/xhr.cache.js
|
||||
- src/service/xhr.error.js
|
||||
- src/service/xhr.js
|
||||
- src/service/locale.js
|
||||
- src/directives.js
|
||||
- src/markups.js
|
||||
- src/widgets.js
|
||||
- src/angular-mocks.js
|
||||
- perf/data/*.js
|
||||
- perf/testUtils.js
|
||||
- perf/*.js
|
||||
|
||||
exclude:
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
@@ -1,10 +0,0 @@
|
||||
server: http://localhost:9877
|
||||
|
||||
load:
|
||||
- build/angular-scenario.js
|
||||
- build/jstd-scenario-adapter-config.js
|
||||
- build/jstd-scenario-adapter.js
|
||||
- build/docs/docs-scenario.js
|
||||
|
||||
proxy:
|
||||
- {matcher: "*", server: "http://localhost:8000"}
|
||||
@@ -1,62 +0,0 @@
|
||||
server: http://localhost:9876
|
||||
|
||||
load:
|
||||
- lib/jasmine-1.0.1/jasmine.js
|
||||
- lib/jasmine-jstd-adapter/JasmineAdapter.js
|
||||
- lib/jquery/jquery-1.4.2.js
|
||||
- test/jquery_remove.js
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/Compiler.js
|
||||
- src/Scope.js
|
||||
- src/Injector.js
|
||||
- src/parser.js
|
||||
- src/Resource.js
|
||||
- src/Browser.js
|
||||
- src/sanitizer.js
|
||||
- src/jqLite.js
|
||||
- src/apis.js
|
||||
- src/filters.js
|
||||
- src/formatters.js
|
||||
- src/validators.js
|
||||
- src/service/cookieStore.js
|
||||
- src/service/cookies.js
|
||||
- src/service/defer.js
|
||||
- src/service/document.js
|
||||
- src/service/exceptionHandler.js
|
||||
- src/service/hover.js
|
||||
- src/service/invalidWidgets.js
|
||||
- src/service/location.js
|
||||
- src/service/log.js
|
||||
- src/service/resource.js
|
||||
- src/service/route.js
|
||||
- src/service/routeParams.js
|
||||
- src/service/window.js
|
||||
- src/service/xhr.bulk.js
|
||||
- src/service/xhr.cache.js
|
||||
- src/service/xhr.error.js
|
||||
- src/service/xhr.js
|
||||
- src/service/locale.js
|
||||
- src/directives.js
|
||||
- src/markups.js
|
||||
- src/widgets.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/jstd-scenario-adapter/*.js
|
||||
- src/scenario/*.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/jstd-scenario-adapter/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
- test/jquery_alias.js
|
||||
- src/angular-bootstrap.js
|
||||
- src/scenario/angular-bootstrap.js
|
||||
- src/AngularPublic.js
|
||||
Vendored
-6240
File diff suppressed because it is too large
Load Diff
Vendored
-154
@@ -1,154 +0,0 @@
|
||||
/*!
|
||||
* 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);
|
||||
Vendored
+9046
File diff suppressed because it is too large
Load Diff
Vendored
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
1.6.4
|
||||
@@ -91,6 +91,18 @@ StaticServlet.prototype.handleRequest = function(req, res) {
|
||||
var parts = path.split('/');
|
||||
if (parts[parts.length-1].charAt(0) === '.')
|
||||
return self.sendForbidden_(req, res, path);
|
||||
|
||||
// docs rewriting
|
||||
var REWRITE = /\/(guide|api|cookbook|misc|tutorial)\/.*$/,
|
||||
IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
|
||||
match;
|
||||
|
||||
if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
|
||||
path = path.replace(match[0], '/index.html');
|
||||
sys.puts('Rewrite to ' + path);
|
||||
}
|
||||
// end of docs rewriting
|
||||
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err)
|
||||
return self.sendMissing_(req, res, path);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
node gen_jstd_configs.js
|
||||
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 90000
|
||||
|
||||
+7
-7
@@ -613,8 +613,8 @@ function isLeafNode (node) {
|
||||
* </doc:source>
|
||||
* <doc:scenario>
|
||||
it('should print that initialy the form object is NOT equal to master', function() {
|
||||
expect(element('.doc-example-live input[name=master.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name=master.name]').val()).toBe('world');
|
||||
expect(element('.doc-example-live input[name="master.salutation"]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name="master.name"]').val()).toBe('world');
|
||||
expect(element('.doc-example-live span').css('display')).toBe('inline');
|
||||
});
|
||||
|
||||
@@ -705,8 +705,8 @@ function copy(source, destination){
|
||||
* </doc:source>
|
||||
* <doc:scenario>
|
||||
it('should print that initialy greeting is equal to the hardcoded value object', function() {
|
||||
expect(element('.doc-example-live input[name=greeting.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name=greeting.name]').val()).toBe('world');
|
||||
expect(element('.doc-example-live input[name="greeting.salutation"]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name="greeting.name"]').val()).toBe('world');
|
||||
expect(element('.doc-example-live span').css('display')).toBe('none');
|
||||
});
|
||||
|
||||
@@ -859,8 +859,8 @@ function parseKeyValue(/**string*/keyValue) {
|
||||
forEach((keyValue || "").split('&'), function(keyValue){
|
||||
if (keyValue) {
|
||||
key_value = keyValue.split('=');
|
||||
key = unescape(key_value[0]);
|
||||
obj[key] = isDefined(key_value[1]) ? unescape(key_value[1]) : true;
|
||||
key = decodeURIComponent(key_value[0]);
|
||||
obj[key] = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
@@ -869,7 +869,7 @@ function parseKeyValue(/**string*/keyValue) {
|
||||
function toKeyValue(obj) {
|
||||
var parts = [];
|
||||
forEach(obj, function(value, key) {
|
||||
parts.push(escape(key) + (value === true ? '' : '=' + escape(value)));
|
||||
parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
|
||||
});
|
||||
return parts.length ? parts.join('&') : '';
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
var browserSingleton;
|
||||
|
||||
angularService('$browser', function($log){
|
||||
angularService('$browser', function($log, $sniffer) {
|
||||
if (!browserSingleton) {
|
||||
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
|
||||
XHR, $log);
|
||||
XHR, $log, $sniffer);
|
||||
browserSingleton.bind();
|
||||
}
|
||||
return browserSingleton;
|
||||
}, {$inject:['$log']});
|
||||
}, {$inject: ['$log', '$sniffer']});
|
||||
|
||||
|
||||
extend(angular, {
|
||||
|
||||
+90
-53
@@ -34,15 +34,16 @@ var XHR = window.XMLHttpRequest || function () {
|
||||
* @param {object} body jQuery wrapped document.body.
|
||||
* @param {function()} XHR XMLHttpRequest constructor.
|
||||
* @param {object} $log console.log or an object with the same interface.
|
||||
* @param {object} $sniffer $sniffer service
|
||||
*/
|
||||
function Browser(window, document, body, XHR, $log) {
|
||||
function Browser(window, document, body, XHR, $log, $sniffer) {
|
||||
var self = this,
|
||||
rawDocument = document[0],
|
||||
location = window.location,
|
||||
history = window.history,
|
||||
setTimeout = window.setTimeout,
|
||||
clearTimeout = window.clearTimeout,
|
||||
pendingDeferIds = {},
|
||||
lastLocationUrl;
|
||||
pendingDeferIds = {};
|
||||
|
||||
self.isMock = false;
|
||||
|
||||
@@ -122,7 +123,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
|
||||
var status = xhr.status == 1223 ? 204 : xhr.status || 200;
|
||||
var status = xhr.status == 1223 ? 204 : xhr.status;
|
||||
completeOutstandingRequest(callback, status, xhr.responseText);
|
||||
}
|
||||
};
|
||||
@@ -194,78 +195,103 @@ function Browser(window, document, body, XHR, $log) {
|
||||
// URL API
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
var lastBrowserUrl = location.href;
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#setUrl
|
||||
* @name angular.service.$browser#url
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @param {string} url New url
|
||||
*
|
||||
* @description
|
||||
* Sets browser's url
|
||||
* GETTER:
|
||||
* Without any argument, this method just returns current value of location.href.
|
||||
*
|
||||
* SETTER:
|
||||
* With at least one argument, this method sets url to new value.
|
||||
* If html5 history api supported, pushState/replaceState is used, otherwise
|
||||
* location.href/location.replace is used.
|
||||
* Returns its own instance to allow chaining
|
||||
*
|
||||
* NOTE: this api is intended for use only by the $location service. Please use the
|
||||
* {@link angular.service.$location $location service} to change url.
|
||||
*
|
||||
* @param {string} url New url (when used as setter)
|
||||
* @param {boolean=} replace Should new url replace current history record ?
|
||||
*/
|
||||
self.setUrl = function(url) {
|
||||
|
||||
var existingURL = lastLocationUrl;
|
||||
if (!existingURL.match(/#/)) existingURL += '#';
|
||||
if (!url.match(/#/)) url += '#';
|
||||
if (existingURL != url) {
|
||||
location.href = url;
|
||||
self.url = function(url, replace) {
|
||||
// setter
|
||||
if (url) {
|
||||
lastBrowserUrl = url;
|
||||
if ($sniffer.history) {
|
||||
if (replace) history.replaceState(null, '', url);
|
||||
else history.pushState(null, '', url);
|
||||
} else {
|
||||
if (replace) location.replace(url);
|
||||
else location.href = url;
|
||||
}
|
||||
return self;
|
||||
// getter
|
||||
} else {
|
||||
return location.href;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#getUrl
|
||||
* @methodOf angular.service.$browser
|
||||
*
|
||||
* @description
|
||||
* Get current browser's url
|
||||
*
|
||||
* @returns {string} Browser's url
|
||||
*/
|
||||
self.getUrl = function() {
|
||||
return lastLocationUrl = location.href;
|
||||
};
|
||||
|
||||
var urlChangeListeners = [],
|
||||
urlChangeInit = false;
|
||||
|
||||
function fireUrlChange() {
|
||||
if (lastBrowserUrl == self.url()) return;
|
||||
|
||||
lastBrowserUrl = self.url();
|
||||
forEach(urlChangeListeners, function(listener) {
|
||||
listener(self.url());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$browser#onHashChange
|
||||
* @name angular.service.$browser#onUrlChange
|
||||
* @methodOf angular.service.$browser
|
||||
* @TODO(vojta): refactor to use node's syntax for events
|
||||
*
|
||||
* @description
|
||||
* Detects if browser support onhashchange events and register a listener otherwise registers
|
||||
* $browser poller. The `listener` will then get called when the hash changes.
|
||||
* Register callback function that will be called, when url changes.
|
||||
*
|
||||
* The listener gets called with either HashChangeEvent object or simple object that also contains
|
||||
* `oldURL` and `newURL` properties.
|
||||
* It's only called when the url is changed by outside of angular:
|
||||
* - user types different url into address bar
|
||||
* - user clicks on history (forward/back) button
|
||||
* - user clicks on a link
|
||||
*
|
||||
* Note: this api is intended for use only by the $location service. Please use the
|
||||
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
|
||||
* It's not called when url is changed by $browser.url() method
|
||||
*
|
||||
* @param {function(event)} listener Listener function to be called when url hash changes.
|
||||
* @return {function()} Returns the registered listener fn - handy if the fn is anonymous.
|
||||
* The listener gets called with new url as parameter.
|
||||
*
|
||||
* NOTE: this api is intended for use only by the $location service. Please use the
|
||||
* {@link angular.service.$location $location service} to monitor url changes in angular apps.
|
||||
*
|
||||
* @param {function(string)} listener Listener function to be called when url changes.
|
||||
* @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
|
||||
*/
|
||||
self.onHashChange = function(listener) {
|
||||
// IE8 comp mode returns true, but doesn't support hashchange event
|
||||
var dm = window.document.documentMode;
|
||||
if ('onhashchange' in window && (isUndefined(dm) || dm >= 8)) {
|
||||
jqLite(window).bind('hashchange', listener);
|
||||
} else {
|
||||
var lastBrowserUrl = self.getUrl();
|
||||
self.onUrlChange = function(callback) {
|
||||
if (!urlChangeInit) {
|
||||
// We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
|
||||
// don't fire popstate when user change the address bar and don't fire hashchange when url
|
||||
// changed by push/replaceState
|
||||
|
||||
self.addPollFn(function() {
|
||||
if (lastBrowserUrl != self.getUrl()) {
|
||||
listener();
|
||||
lastBrowserUrl = self.getUrl();
|
||||
}
|
||||
});
|
||||
// html5 history api - popstate event
|
||||
if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
|
||||
// hashchange event
|
||||
if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
|
||||
// polling
|
||||
else self.addPollFn(fireUrlChange);
|
||||
|
||||
urlChangeInit = true;
|
||||
}
|
||||
return listener;
|
||||
|
||||
urlChangeListeners.push(callback);
|
||||
return callback;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
@@ -483,4 +509,15 @@ function Browser(window, document, body, XHR, $log) {
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns current <base href>
|
||||
* (always relative - without domain)
|
||||
*
|
||||
* @returns {string=}
|
||||
*/
|
||||
self.baseHref = function() {
|
||||
var href = document.find('base').attr('href');
|
||||
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ ResourceFactory.prototype = {
|
||||
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
|
||||
self.xhr(
|
||||
action.method,
|
||||
route.url(extend({}, action.params || {}, extractParams(data), params)),
|
||||
route.url(extend({}, extractParams(data), action.params || {}, params)),
|
||||
data,
|
||||
function(status, response) {
|
||||
if (response) {
|
||||
|
||||
Vendored
+11
-47
@@ -64,7 +64,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
function addScripts(){
|
||||
window.addScripts = function(scripts) {
|
||||
delete window.addScripts;
|
||||
delete window.angularFiles;
|
||||
|
||||
var prop, i;
|
||||
|
||||
// initialize the window property cache
|
||||
@@ -75,8 +78,8 @@
|
||||
}
|
||||
|
||||
// load the js scripts
|
||||
for (i in Array.prototype.slice.call(arguments, 0)) {
|
||||
var file = arguments[i];
|
||||
for (i in scripts) {
|
||||
var file = scripts[i].replace(/src\//, '');
|
||||
document.write('<script type="text/javascript" src="' + serverPath + file + '" ' +
|
||||
'onload="angularClobberTest(\'' + file + '\')"></script>');
|
||||
}
|
||||
@@ -89,49 +92,10 @@
|
||||
|
||||
addCss('angular.css');
|
||||
|
||||
addScripts('Angular.js',
|
||||
'JSON.js',
|
||||
'Compiler.js',
|
||||
'Scope.js',
|
||||
'Injector.js',
|
||||
'jqLite.js',
|
||||
'parser.js',
|
||||
'Resource.js',
|
||||
'Browser.js',
|
||||
'sanitizer.js',
|
||||
'AngularPublic.js',
|
||||
document.write('<script type="text/javascript" src="' + serverPath + '../angularFiles.js' + '" ' +
|
||||
'onload="addScripts(angularFiles.angularSrc)"></script>');
|
||||
|
||||
// Extension points
|
||||
|
||||
'service/cookieStore.js',
|
||||
'service/cookies.js',
|
||||
'service/defer.js',
|
||||
'service/document.js',
|
||||
'service/exceptionHandler.js',
|
||||
'service/hover.js',
|
||||
'service/invalidWidgets.js',
|
||||
'service/location.js',
|
||||
'service/log.js',
|
||||
'service/resource.js',
|
||||
'service/route.js',
|
||||
'service/routeParams.js',
|
||||
'service/window.js',
|
||||
'service/xhr.bulk.js',
|
||||
'service/xhr.cache.js',
|
||||
'service/xhr.error.js',
|
||||
'service/xhr.js',
|
||||
'service/locale.js',
|
||||
|
||||
'apis.js',
|
||||
'filters.js',
|
||||
'formatters.js',
|
||||
'validators.js',
|
||||
'directives.js',
|
||||
'markups.js',
|
||||
'widgets.js');
|
||||
|
||||
|
||||
function onLoadListener(){
|
||||
function onLoadListener() {
|
||||
// empty the cache to prevent mem leaks
|
||||
globalVars = {};
|
||||
|
||||
@@ -143,9 +107,9 @@
|
||||
angularInit(config, document);
|
||||
}
|
||||
|
||||
if (window.addEventListener){
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', onLoadListener, false);
|
||||
} else if (window.attachEvent){
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', onLoadListener);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+19
-13
@@ -89,19 +89,19 @@ function MockBrowser() {
|
||||
requests = [];
|
||||
|
||||
this.isMock = true;
|
||||
self.url = "http://server";
|
||||
self.lastUrl = self.url; // used by url polling fn
|
||||
self.$$url = "http://server";
|
||||
self.$$lastUrl = self.$$url; // used by url polling fn
|
||||
self.pollFns = [];
|
||||
|
||||
|
||||
// register url polling fn
|
||||
|
||||
self.onHashChange = function(listener) {
|
||||
self.onUrlChange = function(listener) {
|
||||
self.pollFns.push(
|
||||
function() {
|
||||
if (self.lastUrl != self.url) {
|
||||
self.lastUrl = self.url;
|
||||
listener();
|
||||
if (self.$$lastUrl != self.$$url) {
|
||||
self.$$lastUrl = self.$$url;
|
||||
listener(self.$$url);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -279,11 +279,11 @@ function MockBrowser() {
|
||||
self.defer.cancel = function(deferId) {
|
||||
var fnIndex;
|
||||
|
||||
forEach(self.deferredFns, function(fn, index) {
|
||||
angular.forEach(self.deferredFns, function(fn, index) {
|
||||
if (fn.id === deferId) fnIndex = index;
|
||||
});
|
||||
|
||||
if (fnIndex) {
|
||||
if (fnIndex !== undefined) {
|
||||
self.deferredFns.splice(fnIndex, 1);
|
||||
}
|
||||
};
|
||||
@@ -302,6 +302,11 @@ function MockBrowser() {
|
||||
self.deferredFns.shift().fn();
|
||||
}
|
||||
};
|
||||
|
||||
self.$$baseHref = '';
|
||||
self.baseHref = function() {
|
||||
return this.$$baseHref;
|
||||
};
|
||||
}
|
||||
MockBrowser.prototype = {
|
||||
|
||||
@@ -326,12 +331,13 @@ MockBrowser.prototype = {
|
||||
hover: function(onHover) {
|
||||
},
|
||||
|
||||
getUrl: function(){
|
||||
return this.url;
|
||||
},
|
||||
url: function(url, replace) {
|
||||
if (url) {
|
||||
this.$$url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
setUrl: function(url){
|
||||
this.url = url;
|
||||
return this.$$url;
|
||||
},
|
||||
|
||||
cookies: function(name, value) {
|
||||
|
||||
+18
-2
@@ -697,8 +697,9 @@ var angularArray = {
|
||||
* @param {string|Number} limit The length of the returned array. If the `limit` number is
|
||||
* positive, `limit` number of items from the beginning of the source array are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array are
|
||||
* copied.
|
||||
* @returns {Array} A new sub-array of length `limit`.
|
||||
* copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
|
||||
* elements.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
@@ -718,6 +719,11 @@ var angularArray = {
|
||||
input('limit').enter(-3);
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]');
|
||||
});
|
||||
|
||||
it('should not exceed the maximum size of input array', function() {
|
||||
input('limit').enter(100);
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3,4,5,6,7,8,9]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
@@ -726,6 +732,16 @@ var angularArray = {
|
||||
var out = [],
|
||||
i, n;
|
||||
|
||||
// check that array is iterable
|
||||
if (!array || !(array instanceof Array))
|
||||
return out;
|
||||
|
||||
// if abs(limit) exceeds maximum length, trim it
|
||||
if (limit > array.length)
|
||||
limit = array.length;
|
||||
else if (limit < -array.length)
|
||||
limit = -array.length;
|
||||
|
||||
if (limit > 0) {
|
||||
i = 0;
|
||||
n = limit;
|
||||
|
||||
+10
-8
@@ -588,17 +588,17 @@ function ngClass(selector) {
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class', function(){
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
expect(element('.doc-example-live span').prop('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:first').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).
|
||||
expect(element('.doc-example-live span').prop('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:last').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
expect(element('.doc-example-live span').prop('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -637,9 +637,9 @@ angularDirective("ng:class", ngClass(function(){return true;}));
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
expect(element('.doc-example-live li:first span').prop('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
expect(element('.doc-example-live li:last span').prop('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -678,9 +678,9 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
expect(element('.doc-example-live li:first span').prop('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
expect(element('.doc-example-live li:last span').prop('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -796,7 +796,7 @@ angularDirective("ng:hide", function(expression, element){
|
||||
it('should check ng:style', function(){
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
element('.doc-example-live :button[value=set]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('red');
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
|
||||
element('.doc-example-live :button[value=clear]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
});
|
||||
@@ -804,6 +804,8 @@ angularDirective("ng:hide", function(expression, element){
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:style", function(expression, element){
|
||||
// TODO(i): this is inefficient (runs on every $digest) and obtrusive (overrides 3rd part css)
|
||||
// we should change it in a similar way as I changed ng:class
|
||||
return function(element){
|
||||
var resetStyle = getStyle(element);
|
||||
this.$watch(function(scope){
|
||||
|
||||
+12
-11
@@ -61,7 +61,7 @@
|
||||
input('amount').enter('-1234');
|
||||
expect(binding('amount | currency')).toBe('($1,234.00)');
|
||||
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
|
||||
expect(element('.doc-example-live .ng-binding').attr('className')).
|
||||
expect(element('.doc-example-live .ng-binding').prop('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -71,8 +71,8 @@ angularFilter.currency = function(amount, currencySymbol){
|
||||
var formats = this.$service('$locale').NUMBER_FORMATS;
|
||||
this.$element.toggleClass('ng-format-negative', amount < 0);
|
||||
if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
|
||||
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2)
|
||||
.replace(/\u00A4/g, currencySymbol);
|
||||
return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
|
||||
replace(/\u00A4/g, currencySymbol);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -118,16 +118,17 @@ angularFilter.currency = function(amount, currencySymbol){
|
||||
var DECIMAL_SEP = '.';
|
||||
|
||||
angularFilter.number = function(number, fractionSize) {
|
||||
if (isNaN(number) || !isFinite(number)) return '';
|
||||
var formats = this.$service('$locale').NUMBER_FORMATS;
|
||||
return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP,
|
||||
formats.DECIMAL_SEP, fractionSize);
|
||||
}
|
||||
};
|
||||
|
||||
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
if (isNaN(number) || !isFinite(number)) return '';
|
||||
|
||||
var isNegative = number < 0;
|
||||
number = Math.abs(number);
|
||||
var numStr = number + '',
|
||||
var numStr = number + '',
|
||||
formatedText = '',
|
||||
parts = [];
|
||||
|
||||
@@ -136,7 +137,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
|
||||
} else {
|
||||
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
|
||||
|
||||
//determine fractionSize if it is not specified
|
||||
// determine fractionSize if it is not specified
|
||||
if (isUndefined(fractionSize)) {
|
||||
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
|
||||
}
|
||||
@@ -248,10 +249,10 @@ var DATE_FORMATS = {
|
||||
Z: timeZoneGetter
|
||||
};
|
||||
|
||||
var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/;
|
||||
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/
|
||||
var OPERA_TOSTRING_PATTERN = /^[\d].*Z$/;
|
||||
var NUMBER_STRING = /^\d+$/;
|
||||
var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/,
|
||||
DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
|
||||
OPERA_TOSTRING_PATTERN = /^[\d].*Z$/,
|
||||
NUMBER_STRING = /^\d+$/;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+81
-11
@@ -37,8 +37,10 @@
|
||||
* - [clone()](http://api.jquery.com/clone/)
|
||||
* - [css()](http://api.jquery.com/css/)
|
||||
* - [data()](http://api.jquery.com/data/)
|
||||
* - [eq()](http://api.jquery.com/eq/)
|
||||
* - [hasClass()](http://api.jquery.com/hasClass/)
|
||||
* - [parent()](http://api.jquery.com/parent/)
|
||||
* - [prop()](http://api.jquery.com/prop/)
|
||||
* - [remove()](http://api.jquery.com/remove/)
|
||||
* - [removeAttr()](http://api.jquery.com/removeAttr/)
|
||||
* - [removeClass()](http://api.jquery.com/removeClass/)
|
||||
@@ -46,7 +48,7 @@
|
||||
* - [replaceWith()](http://api.jquery.com/replaceWith/)
|
||||
* - [text()](http://api.jquery.com/text/)
|
||||
* - [trigger()](http://api.jquery.com/trigger/)
|
||||
* - [eq()](http://api.jquery.com/eq/)
|
||||
* - [unbind()](http://api.jquery.com/unbind/)
|
||||
*
|
||||
* ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
|
||||
*
|
||||
@@ -86,6 +88,16 @@ function getStyle(element) {
|
||||
return current;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts dash-separated names to camelCase. Useful for dealing with css properties.
|
||||
*/
|
||||
function camelCase(name) {
|
||||
return name.replace(/\-(\w)/g, function(all, letter, offset){
|
||||
return (offset == 0 && letter == 'w') ? 'w' : letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
function jqLiteWrap(element) {
|
||||
if (isString(element) && element.charAt(0) != '<') {
|
||||
@@ -245,25 +257,42 @@ forEach({
|
||||
hasClass: JQLiteHasClass,
|
||||
|
||||
css: function(element, name, value) {
|
||||
name = camelCase(name);
|
||||
|
||||
if (isDefined(value)) {
|
||||
element.style[name] = value;
|
||||
} else {
|
||||
return element.style[name];
|
||||
var val;
|
||||
|
||||
if (msie <= 8) {
|
||||
// this is some IE specific weirdness that jQuery 1.6.4 does not sure why
|
||||
val = element.currentStyle && element.currentStyle[name];
|
||||
if (val === '') val = 'auto';
|
||||
}
|
||||
|
||||
val = val || element.style[name];
|
||||
|
||||
if (msie <= 8) {
|
||||
// jquery weirdness :-/
|
||||
val = (val === '') ? undefined : val;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
},
|
||||
|
||||
attr: function(element, name, value){
|
||||
if (name === 'class') {
|
||||
if(isDefined(value)) {
|
||||
element.className = value;
|
||||
} else {
|
||||
return element.className;
|
||||
}
|
||||
} else if (SPECIAL_ATTR[name]) {
|
||||
if (SPECIAL_ATTR[name]) {
|
||||
if (isDefined(value)) {
|
||||
element[name] = !!value;
|
||||
if (!!value) {
|
||||
element[name] = true;
|
||||
element.setAttribute(name, name);
|
||||
} else {
|
||||
element[name] = false;
|
||||
element.removeAttribute(name);
|
||||
}
|
||||
} else {
|
||||
return element[name];
|
||||
return (element[name] || element.getAttribute(name)) ? name : undefined;
|
||||
}
|
||||
} else if (isDefined(value)) {
|
||||
element.setAttribute(name, value);
|
||||
@@ -276,6 +305,14 @@ forEach({
|
||||
}
|
||||
},
|
||||
|
||||
prop: function(element, name, value) {
|
||||
if (isDefined(value)) {
|
||||
element[name] = value;
|
||||
} else {
|
||||
return element[name];
|
||||
}
|
||||
},
|
||||
|
||||
text: extend((msie < 9)
|
||||
? function(element, value) {
|
||||
// NodeType == 3 is text node
|
||||
@@ -376,6 +413,20 @@ forEach({
|
||||
if (!event.target) {
|
||||
event.target = event.srcElement || document;
|
||||
}
|
||||
|
||||
if (isUndefined(event.defaultPrevented)) {
|
||||
var prevent = event.preventDefault;
|
||||
event.preventDefault = function() {
|
||||
event.defaultPrevented = true;
|
||||
prevent.call(event);
|
||||
};
|
||||
event.defaultPrevented = false;
|
||||
}
|
||||
|
||||
event.isDefaultPrevented = function() {
|
||||
return event.defaultPrevented;
|
||||
};
|
||||
|
||||
forEach(eventHandler.fns, function(fn){
|
||||
fn.call(element, event);
|
||||
});
|
||||
@@ -387,6 +438,25 @@ forEach({
|
||||
});
|
||||
},
|
||||
|
||||
unbind: function(element, type, fn) {
|
||||
var bind = JQLiteData(element, 'bind');
|
||||
if (!bind) return; //no listeners registered
|
||||
|
||||
if (isUndefined(type)) {
|
||||
forEach(bind, function(eventHandler, type) {
|
||||
removeEventListenerFn(element, type, eventHandler);
|
||||
delete bind[type];
|
||||
});
|
||||
} else {
|
||||
if (isUndefined(fn)) {
|
||||
removeEventListenerFn(element, type, bind[type]);
|
||||
delete bind[type];
|
||||
} else {
|
||||
angularArray.remove(bind[type].fns, fn);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
replaceWith: function(element, replaceNode) {
|
||||
var index, parent = element.parentNode;
|
||||
JQLiteDealoc(element);
|
||||
|
||||
+18
-17
@@ -166,10 +166,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
<input name="value" /><br />
|
||||
<a id="link-1" href ng:click="value = 1">link 1</a> (link, don't reload)<br />
|
||||
<a id="link-2" href="" ng:click="value = 2">link 2</a> (link, don't reload)<br />
|
||||
<a id="link-3" ng:href="#{{'123'}}" ng:click="value = 3">link 3</a> (link, reload!)<br />
|
||||
<a id="link-3" ng:href="/{{'123'}}" ng:ext-link>link 3</a> (link, reload!)<br />
|
||||
<a id="link-4" href="" name="xx" ng:click="value = 4">anchor</a> (link, don't reload)<br />
|
||||
<a id="link-5" name="xxx" ng:click="value = 5">anchor</a> (no link)<br />
|
||||
<a id="link-6" ng:href="#/{{value}}">link</a> (link, change hash)
|
||||
<a id="link-6" ng:href="/{{value}}" ng:ext-link>link</a> (link, change hash)
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should execute ng:click but not reload when href without value', function() {
|
||||
@@ -185,10 +185,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
});
|
||||
|
||||
it('should execute ng:click and change url when ng:href specified', function() {
|
||||
expect(element('#link-3').attr('href')).toBe("/123");
|
||||
|
||||
element('#link-3').click();
|
||||
expect(input('value').val()).toEqual('3');
|
||||
expect(element('#link-3').attr('href')).toBe("#123");
|
||||
expect(browser().location().hash()).toEqual('123');
|
||||
expect(browser().location().path()).toEqual('/123');
|
||||
});
|
||||
|
||||
it('should execute ng:click but not reload when href empty string and name specified', function() {
|
||||
@@ -205,9 +205,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
|
||||
it('should only change url when only ng:href', function() {
|
||||
input('value').enter('6');
|
||||
expect(element('#link-6').attr('href')).toBe("/6");
|
||||
|
||||
element('#link-6').click();
|
||||
expect(browser().location().hash()).toEqual('/6');
|
||||
expect(element('#link-6').attr('href')).toBe("#/6");
|
||||
expect(browser().location().path()).toEqual('/6');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -266,9 +267,9 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should toggle button', function() {
|
||||
expect(element('.doc-example-live :button').attr('disabled')).toBeFalsy();
|
||||
expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
|
||||
input('checked').check();
|
||||
expect(element('.doc-example-live :button').attr('disabled')).toBeTruthy();
|
||||
expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -296,9 +297,9 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check both checkBoxes', function() {
|
||||
expect(element('.doc-example-live #checkSlave').attr('checked')).toBeFalsy();
|
||||
expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
|
||||
input('master').check();
|
||||
expect(element('.doc-example-live #checkSlave').attr('checked')).toBeTruthy();
|
||||
expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -332,9 +333,9 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should toggle multiple', function() {
|
||||
expect(element('.doc-example-live #select').attr('multiple')).toBeFalsy();
|
||||
expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
|
||||
input('checked').check();
|
||||
expect(element('.doc-example-live #select').attr('multiple')).toBeTruthy();
|
||||
expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -362,9 +363,9 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should toggle readonly attr', function() {
|
||||
expect(element('.doc-example-live :text').attr('readonly')).toBeFalsy();
|
||||
expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
|
||||
input('checked').check();
|
||||
expect(element('.doc-example-live :text').attr('readonly')).toBeTruthy();
|
||||
expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -395,9 +396,9 @@ angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should select Greetings!', function() {
|
||||
expect(element('.doc-example-live #greet').attr('selected')).toBeFalsy();
|
||||
expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
|
||||
input('checked').check();
|
||||
expect(element('.doc-example-live #greet').attr('selected')).toBeTruthy();
|
||||
expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
+19
-19
@@ -5,25 +5,25 @@ var OPERATORS = {
|
||||
'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){a=a(self); b=b(self); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
|
||||
'-':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
|
||||
'*':function(self, a,b){return a(self)*b(self);},
|
||||
'/':function(self, a,b){return a(self)/b(self);},
|
||||
'%':function(self, a,b){return a(self)%b(self);},
|
||||
'^':function(self, a,b){return a(self)^b(self);},
|
||||
'=':noop,
|
||||
'==':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(self)==b(self);},
|
||||
'!=':function(self, a,b){return a(self)!=b(self);},
|
||||
'<':function(self, a,b){return a(self)<b(self);},
|
||||
'>':function(self, a,b){return a(self)>b(self);},
|
||||
'<=':function(self, a,b){return a(self)<=b(self);},
|
||||
'>=':function(self, a,b){return a(self)>=b(self);},
|
||||
'&&':function(self, a,b){return a(self)&&b(self);},
|
||||
'||':function(self, a,b){return a(self)||b(self);},
|
||||
'&':function(self, a,b){return a(self)&b(self);},
|
||||
// '|':function(self, a,b){return a|b;},
|
||||
'|':function(self, a,b){return b(self, a);},
|
||||
'!':function(self, a){return !a;}
|
||||
'|':function(self, a,b){return b(self)(self, a(self));},
|
||||
'!':function(self, a){return !a(self);}
|
||||
};
|
||||
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
|
||||
|
||||
@@ -308,13 +308,13 @@ function parser(text, json){
|
||||
|
||||
function unaryFn(fn, right) {
|
||||
return function(self) {
|
||||
return fn(self, right(self));
|
||||
return fn(self, right);
|
||||
};
|
||||
}
|
||||
|
||||
function binaryFn(left, fn, right) {
|
||||
return function(self) {
|
||||
return fn(self, left(self), right(self));
|
||||
return fn(self, left, right);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ angular.scenario.Application.prototype.getFrame_ = function() {
|
||||
* @return {Object} the window of the frame
|
||||
*/
|
||||
angular.scenario.Application.prototype.getWindow_ = function() {
|
||||
var contentWindow = this.getFrame_().attr('contentWindow');
|
||||
var contentWindow = this.getFrame_().prop('contentWindow');
|
||||
if (!contentWindow)
|
||||
throw 'Frame window is not accessible.';
|
||||
return contentWindow;
|
||||
|
||||
@@ -267,7 +267,7 @@ function browserTrigger(element, type) {
|
||||
// forcing the browser to compute the element position (by reading its CSS)
|
||||
// puts the element in consistent state.
|
||||
element.style.posLeft;
|
||||
element.fireEvent('on' + type);
|
||||
var ret = element.fireEvent('on' + type);
|
||||
if (lowercase(element.type) == 'submit') {
|
||||
while(element) {
|
||||
if (lowercase(element.nodeName) == 'form') {
|
||||
@@ -277,10 +277,30 @@ function browserTrigger(element, type) {
|
||||
element = element.parentNode;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
var evnt = document.createEvent('MouseEvents');
|
||||
var evnt = document.createEvent('MouseEvents'),
|
||||
originalPreventDefault = evnt.preventDefault,
|
||||
iframe = _jQuery('#application iframe')[0],
|
||||
appWindow = iframe ? iframe.contentWindow : window,
|
||||
fakeProcessDefault = true,
|
||||
finalProcessDefault;
|
||||
|
||||
// igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208
|
||||
appWindow.angular['ff-684208-preventDefault'] = false;
|
||||
evnt.preventDefault = function() {
|
||||
fakeProcessDefault = false;
|
||||
return originalPreventDefault.apply(evnt, arguments);
|
||||
};
|
||||
|
||||
evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, element);
|
||||
|
||||
element.dispatchEvent(evnt);
|
||||
finalProcessDefault = !(appWindow.angular['ff-684208-preventDefault'] || !fakeProcessDefault)
|
||||
|
||||
delete appWindow.angular['ff-684208-preventDefault'];
|
||||
|
||||
return finalProcessDefault;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,9 +317,14 @@ function browserTrigger(element, type) {
|
||||
var parentTrigger = fn.trigger;
|
||||
fn.trigger = function(type) {
|
||||
if (/(click|change|keydown)/.test(type)) {
|
||||
return this.each(function(index, node) {
|
||||
browserTrigger(node, type);
|
||||
var processDefaults = [];
|
||||
this.each(function(index, node) {
|
||||
processDefaults.push(browserTrigger(node, type));
|
||||
});
|
||||
|
||||
// this is not compatible with jQuery - we return an array of returned values,
|
||||
// so that scenario runner know whether JS code has preventDefault() of the event or not...
|
||||
return processDefaults;
|
||||
}
|
||||
return parentTrigger.apply(this, arguments);
|
||||
};
|
||||
|
||||
Vendored
+1
-1
@@ -30,7 +30,7 @@
|
||||
};
|
||||
|
||||
addCSS("../../css/angular-scenario.css");
|
||||
addScript("../../lib/jquery/jquery-1.4.2.js");
|
||||
addScript("../../lib/jquery/jquery.js");
|
||||
document.write(
|
||||
'<script type="text/javascript">' +
|
||||
'var _jQuery = jQuery.noConflict(true);' +
|
||||
|
||||
+16
-13
@@ -302,7 +302,7 @@ angular.scenario.dsl('select', function() {
|
||||
* element(selector, label).{method}(key, value) sets the value (as defined by jQuery, ex. attr)
|
||||
*/
|
||||
angular.scenario.dsl('element', function() {
|
||||
var KEY_VALUE_METHODS = ['attr', 'css'];
|
||||
var KEY_VALUE_METHODS = ['attr', 'css', 'prop'];
|
||||
var VALUE_METHODS = [
|
||||
'val', 'text', 'html', 'height', 'innerHeight', 'outerHeight', 'width',
|
||||
'innerWidth', 'outerWidth', 'position', 'scrollLeft', 'scrollTop', 'offset'
|
||||
@@ -323,8 +323,9 @@ angular.scenario.dsl('element', function() {
|
||||
return this.addFutureAction("element '" + this.label + "' click", function($window, $document, done) {
|
||||
var elements = $document.elements();
|
||||
var href = elements.attr('href');
|
||||
elements.trigger('click');
|
||||
if (href && elements[0].nodeName.toUpperCase() === 'A') {
|
||||
var eventProcessDefault = elements.trigger('click')[0];
|
||||
|
||||
if (href && elements[0].nodeName.toUpperCase() === 'A' && eventProcessDefault) {
|
||||
this.application.navigateTo(href, function() {
|
||||
done();
|
||||
}, done);
|
||||
@@ -342,26 +343,28 @@ angular.scenario.dsl('element', function() {
|
||||
|
||||
angular.forEach(KEY_VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(name, value) {
|
||||
var futureName = "element '" + this.label + "' get " + methodName + " '" + name + "'";
|
||||
if (angular.isDefined(value)) {
|
||||
futureName = "element '" + this.label + "' set " + methodName + " '" + name + "' to " + "'" + value + "'";
|
||||
}
|
||||
var args = arguments,
|
||||
futureName = (args.length == 1)
|
||||
? "element '" + this.label + "' get " + methodName + " '" + name + "'"
|
||||
: "element '" + this.label + "' set " + methodName + " '" + name + "' to " + "'" + value + "'";
|
||||
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
var element = $document.elements();
|
||||
done(null, element[methodName].call(element, name, value));
|
||||
done(null, element[methodName].apply(element, args));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
angular.forEach(VALUE_METHODS, function(methodName) {
|
||||
chain[methodName] = function(value) {
|
||||
var futureName = "element '" + this.label + "' " + methodName;
|
||||
if (angular.isDefined(value)) {
|
||||
futureName = "element '" + this.label + "' set " + methodName + " to '" + value + "'";
|
||||
}
|
||||
var args = arguments,
|
||||
futureName = (args.length == 0)
|
||||
? "element '" + this.label + "' " + methodName
|
||||
: futureName = "element '" + this.label + "' set " + methodName + " to '" + value + "'";
|
||||
|
||||
return this.addFutureAction(futureName, function($window, $document, done) {
|
||||
var element = $document.elements();
|
||||
done(null, element[methodName].call(element, value));
|
||||
done(null, element[methodName].apply(element, args));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
+485
-253
@@ -1,275 +1,507 @@
|
||||
'use strict';
|
||||
|
||||
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
|
||||
HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
|
||||
PATH_MATCH = /^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,
|
||||
HASH_MATCH = PATH_MATCH,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21};
|
||||
|
||||
|
||||
/**
|
||||
* Encode path using encodeUriSegment, ignoring forward slashes
|
||||
*
|
||||
* @param {string} path Path to encode
|
||||
* @returns {string}
|
||||
*/
|
||||
function encodePath(path) {
|
||||
var segments = path.split('/'),
|
||||
i = segments.length;
|
||||
|
||||
while (i--) {
|
||||
segments[i] = encodeUriSegment(segments[i]);
|
||||
}
|
||||
|
||||
return segments.join('/');
|
||||
}
|
||||
|
||||
|
||||
function matchUrl(url, obj) {
|
||||
var match = URL_MATCH.exec(url),
|
||||
|
||||
match = {
|
||||
protocol: match[1],
|
||||
host: match[3],
|
||||
port: parseInt(match[5]) || DEFAULT_PORTS[match[1]] || null,
|
||||
path: match[6] || '/',
|
||||
search: match[8],
|
||||
hash: match[10]
|
||||
};
|
||||
|
||||
if (obj) {
|
||||
obj.$$protocol = match.protocol;
|
||||
obj.$$host = match.host;
|
||||
obj.$$port = match.port;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
|
||||
function composeProtocolHostPort(protocol, host, port) {
|
||||
return protocol + '://' + host + (port == DEFAULT_PORTS[protocol] ? '' : ':' + port);
|
||||
}
|
||||
|
||||
|
||||
function pathPrefixFromBase(basePath) {
|
||||
return basePath.substr(0, basePath.lastIndexOf('/'));
|
||||
}
|
||||
|
||||
|
||||
function convertToHtml5Url(url, basePath, hashPrefix) {
|
||||
var match = matchUrl(url);
|
||||
|
||||
// already html5 url
|
||||
if (decodeURIComponent(match.path) != basePath || isUndefined(match.hash) ||
|
||||
match.hash.indexOf(hashPrefix) != 0) {
|
||||
return url;
|
||||
// convert hashbang url -> html5 url
|
||||
} else {
|
||||
return composeProtocolHostPort(match.protocol, match.host, match.port) +
|
||||
pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function convertToHashbangUrl(url, basePath, hashPrefix) {
|
||||
var match = matchUrl(url);
|
||||
|
||||
// already hashbang url
|
||||
if (decodeURIComponent(match.path) == basePath) {
|
||||
return url;
|
||||
// convert html5 url -> hashbang url
|
||||
} else {
|
||||
var search = match.search && '?' + match.search || '',
|
||||
hash = match.hash && '#' + match.hash || '',
|
||||
pathPrefix = pathPrefixFromBase(basePath),
|
||||
path = match.path.substr(pathPrefix.length);
|
||||
|
||||
if (match.path.indexOf(pathPrefix) != 0) {
|
||||
throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !';
|
||||
}
|
||||
|
||||
return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
|
||||
'#' + hashPrefix + path + search + hash;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LocationUrl represents an url
|
||||
* This object is exposed as $location service when html5 is enabled and supported
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} url Html5 url
|
||||
* @param {string} pathPrefix
|
||||
*/
|
||||
function LocationUrl(url, pathPrefix) {
|
||||
pathPrefix = pathPrefix || '';
|
||||
|
||||
/**
|
||||
* Parse given html5 (regular) url string into properties
|
||||
* @param {string} url Html5 url
|
||||
* @private
|
||||
*/
|
||||
this.$$parse = function(url) {
|
||||
var match = matchUrl(url, this);
|
||||
|
||||
if (match.path.indexOf(pathPrefix) != 0) {
|
||||
throw 'Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !';
|
||||
}
|
||||
|
||||
this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
|
||||
this.$$search = parseKeyValue(match.search);
|
||||
this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
|
||||
|
||||
this.$$compose();
|
||||
},
|
||||
|
||||
/**
|
||||
* Compose url and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
|
||||
pathPrefix + this.$$url;
|
||||
};
|
||||
|
||||
this.$$parse(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* LocationHashbangUrl represents url
|
||||
* This object is exposed as $location service when html5 history api is disabled or not supported
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} url Legacy url
|
||||
* @param {string} hashPrefix Prefix for hash part (containing path and search)
|
||||
*/
|
||||
function LocationHashbangUrl(url, hashPrefix) {
|
||||
var basePath;
|
||||
|
||||
/**
|
||||
* Parse given hashbang url into properties
|
||||
* @param {string} url Hashbang url
|
||||
* @private
|
||||
*/
|
||||
this.$$parse = function(url) {
|
||||
var match = matchUrl(url, this);
|
||||
|
||||
if (match.hash && match.hash.indexOf(hashPrefix) != 0) {
|
||||
throw 'Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !';
|
||||
}
|
||||
|
||||
basePath = match.path + (match.search ? '?' + match.search : '');
|
||||
match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
|
||||
if (match[1]) {
|
||||
this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
|
||||
} else {
|
||||
this.$$path = '';
|
||||
}
|
||||
|
||||
this.$$search = parseKeyValue(match[3]);
|
||||
this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
|
||||
|
||||
this.$$compose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Compose hashbang url and update `absUrl` property
|
||||
* @private
|
||||
*/
|
||||
this.$$compose = function() {
|
||||
var search = toKeyValue(this.$$search),
|
||||
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
|
||||
|
||||
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
|
||||
this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
|
||||
basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
|
||||
};
|
||||
|
||||
this.$$parse(url);
|
||||
}
|
||||
|
||||
|
||||
LocationUrl.prototype = LocationHashbangUrl.prototype = {
|
||||
|
||||
/**
|
||||
* Has any change been replacing ?
|
||||
* @private
|
||||
*/
|
||||
$$replace: false,
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#absUrl
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter only.
|
||||
*
|
||||
* Return full url representation with all segments encoded according to rules specified in
|
||||
* {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
absUrl: locationGetter('$$absUrl'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#url
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return url (e.g. `/path?a=b#hash`) when called without any parameter.
|
||||
*
|
||||
* Change path, search and hash, when called with parameter and return `$location`.
|
||||
*
|
||||
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
|
||||
* @return {string}
|
||||
*/
|
||||
url: function(url, replace) {
|
||||
if (isUndefined(url))
|
||||
return this.$$url;
|
||||
|
||||
var match = PATH_MATCH.exec(url);
|
||||
this.path(decodeURIComponent(match[1] || '')).search(match[3] || '')
|
||||
.hash(match[5] || '', replace);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#protocol
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter only.
|
||||
*
|
||||
* Return protocol of current url.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
protocol: locationGetter('$$protocol'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#host
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter only.
|
||||
*
|
||||
* Return host of current url.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
host: locationGetter('$$host'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#port
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter only.
|
||||
*
|
||||
* Return port of current url.
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
port: locationGetter('$$port'),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#path
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return path of current url when called without any parameter.
|
||||
*
|
||||
* Change path when called with parameter and return `$location`.
|
||||
*
|
||||
* Note: Path should always begin with forward slash (/), this method will add the forward slash
|
||||
* if it is missing.
|
||||
*
|
||||
* @param {string=} path New path
|
||||
* @return {string}
|
||||
*/
|
||||
path: locationGetterSetter('$$path', function(path) {
|
||||
return path.charAt(0) == '/' ? path : '/' + path;
|
||||
}),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#search
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return search part (as object) of current url when called without any parameter.
|
||||
*
|
||||
* Change search part when called with parameter and return `$location`.
|
||||
*
|
||||
* @param {string|object<string,string>=} search New search part - string or hash object
|
||||
* @return {string}
|
||||
*/
|
||||
search: function(search, paramValue) {
|
||||
if (isUndefined(search))
|
||||
return this.$$search;
|
||||
|
||||
if (isDefined(paramValue)) {
|
||||
if (paramValue === null) {
|
||||
delete this.$$search[search];
|
||||
} else {
|
||||
this.$$search[search] = encodeUriQuery(paramValue);
|
||||
}
|
||||
} else {
|
||||
this.$$search = isString(search) ? parseKeyValue(search) : search;
|
||||
}
|
||||
|
||||
this.$$compose();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#hash
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* This method is getter / setter.
|
||||
*
|
||||
* Return hash fragment when called without any parameter.
|
||||
*
|
||||
* Change hash fragment when called with parameter and return `$location`.
|
||||
*
|
||||
* @param {string=} hash New hash fragment
|
||||
* @return {string}
|
||||
*/
|
||||
hash: locationGetterSetter('$$hash', identity),
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#replace
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* If called, all changes to $location during current `$digest` will be replacing current history
|
||||
* record, instead of adding new one.
|
||||
*/
|
||||
replace: function() {
|
||||
this.$$replace = true;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function locationGetter(property) {
|
||||
return function() {
|
||||
return this[property];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function locationGetterSetter(property, preprocess) {
|
||||
return function(value) {
|
||||
if (isUndefined(value))
|
||||
return this[property];
|
||||
|
||||
this[property] = preprocess(value);
|
||||
this.$$compose();
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$location
|
||||
* @requires $browser
|
||||
*
|
||||
* @property {string} href The full URL of the current location.
|
||||
* @property {string} protocol The protocol part of the URL (e.g. http or https).
|
||||
* @property {string} host The host name, ip address or FQDN of the current location.
|
||||
* @property {number} port The port number of the current location (e.g. 80, 443, 8080).
|
||||
* @property {string} path The path of the current location (e.g. /myapp/inbox).
|
||||
* @property {Object.<string|boolean>} search Map of query parameters (e.g. {user:"foo", page:23}).
|
||||
* @property {string} hash The fragment part of the URL of the current location (e.g. #foo).
|
||||
* @property {string} hashPath Similar to `path`, but located in the `hash` fragment
|
||||
* (e.g. ../foo#/some/path => /some/path).
|
||||
* @property {Object.<string|boolean>} hashSearch Similar to `search` but located in `hash`
|
||||
* fragment (e.g. .../foo#/some/path?hashQuery=param => {hashQuery: "param"}).
|
||||
* @requires $browser
|
||||
* @requires $sniffer
|
||||
* @requires $config
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
* Parses the browser location url and makes it available to your application.
|
||||
* Any changes to the url are reflected into `$location` service and changes to
|
||||
* `$location` are reflected in the browser location url.
|
||||
* The $location service parses the URL in the browser address bar (based on the {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to your application. Changes to the URL in the address bar are reflected into $location service and changes to $location are reflected into the browser address bar.
|
||||
*
|
||||
* Notice that using browser's forward/back buttons changes the $location.
|
||||
* **The $location service:**
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="$location = $service('$location')">
|
||||
<a id="ex-test" href="#myPath?name=misko">test hash</a>|
|
||||
<a id="ex-reset" href="#!/api/angular.service.$location">reset hash</a><br/>
|
||||
<input type='text' name="$location.hash" size="30">
|
||||
<pre>$location = {{$location}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should initialize the input field', function() {
|
||||
expect(using('.doc-example-live').input('$location.hash').val()).
|
||||
toBe('!/api/angular.service.$location');
|
||||
});
|
||||
|
||||
|
||||
it('should bind $location.hash to the input field', function() {
|
||||
using('.doc-example-live').input('$location.hash').enter('foo');
|
||||
expect(browser().location().hash()).toBe('foo');
|
||||
});
|
||||
|
||||
|
||||
it('should set the hash to a test string with test link is presed', function() {
|
||||
using('.doc-example-live').element('#ex-test').click();
|
||||
expect(using('.doc-example-live').input('$location.hash').val()).
|
||||
toBe('myPath?name=misko');
|
||||
});
|
||||
|
||||
it('should reset $location when reset link is pressed', function() {
|
||||
using('.doc-example-live').input('$location.hash').enter('foo');
|
||||
using('.doc-example-live').element('#ex-reset').click();
|
||||
expect(using('.doc-example-live').input('$location.hash').val()).
|
||||
toBe('!/api/angular.service.$location');
|
||||
});
|
||||
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
* - Exposes the current URL in the browser address bar, so you can
|
||||
* - Watch and observe the URL.
|
||||
* - Change the URL.
|
||||
* - Synchronizes the URL with the browser when the user
|
||||
* - Changes the address bar.
|
||||
* - Clicks the back or forward button (or clicks a History link).
|
||||
* - Clicks on a link.
|
||||
* - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
*
|
||||
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location}
|
||||
*/
|
||||
angularServiceInject("$location", function($browser) {
|
||||
var location = {update: update, updateHash: updateHash};
|
||||
var lastLocation = {}; // last state since last update().
|
||||
angularServiceInject('$location', function($browser, $sniffer, $config, $document) {
|
||||
var scope = this, currentUrl,
|
||||
basePath = $browser.baseHref() || '/',
|
||||
pathPrefix = pathPrefixFromBase(basePath),
|
||||
hashPrefix = $config.hashPrefix || '',
|
||||
initUrl = $browser.url();
|
||||
|
||||
$browser.onHashChange(bind(this, this.$apply, function() { //register
|
||||
update($browser.getUrl());
|
||||
}))(); //initialize
|
||||
|
||||
this.$watch(sync);
|
||||
|
||||
return location;
|
||||
|
||||
// PUBLIC METHODS
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#update
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Updates the location object.
|
||||
* Does not immediately update the browser
|
||||
* Browser is updated at the end of $digest()
|
||||
*
|
||||
* Does not immediately update the browser. Instead the browser is updated at the end of $eval()
|
||||
* cycle.
|
||||
*
|
||||
* <pre>
|
||||
$location.update('http://www.angularjs.org/path#hash?search=x');
|
||||
$location.update({host: 'www.google.com', protocol: 'https'});
|
||||
$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
||||
</pre>
|
||||
*
|
||||
* @param {string|Object} href Full href as a string or object with properties
|
||||
*/
|
||||
function update(href) {
|
||||
if (isString(href)) {
|
||||
extend(location, parseHref(href));
|
||||
if ($config.html5Mode) {
|
||||
if ($sniffer.history) {
|
||||
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
|
||||
} else {
|
||||
if (isDefined(href.hash)) {
|
||||
extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash);
|
||||
}
|
||||
|
||||
extend(location, href);
|
||||
|
||||
if (isDefined(href.hashPath || href.hashSearch)) {
|
||||
location.hash = composeHash(location);
|
||||
}
|
||||
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
$browser.setUrl(location.href);
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#updateHash
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Updates the hash fragment part of the url.
|
||||
*
|
||||
* @see update()
|
||||
*
|
||||
* <pre>
|
||||
scope.$location.updateHash('/hp')
|
||||
==> update({hashPath: '/hp'})
|
||||
scope.$location.updateHash({a: true, b: 'val'})
|
||||
==> update({hashSearch: {a: true, b: 'val'}})
|
||||
scope.$location.updateHash('/hp', {a: true})
|
||||
==> update({hashPath: '/hp', hashSearch: {a: true}})
|
||||
</pre>
|
||||
*
|
||||
* @param {string|Object} path A hashPath or hashSearch object
|
||||
* @param {Object=} search A hashSearch object
|
||||
*/
|
||||
function updateHash(path, search) {
|
||||
var hash = {};
|
||||
|
||||
if (isString(path)) {
|
||||
hash.hashPath = path;
|
||||
hash.hashSearch = search || {};
|
||||
} else
|
||||
hash.hashSearch = path;
|
||||
|
||||
hash.hash = composeHash(hash);
|
||||
|
||||
update({hash: hash});
|
||||
}
|
||||
|
||||
|
||||
// INNER METHODS
|
||||
|
||||
/**
|
||||
* Synchronizes all location object properties.
|
||||
*
|
||||
* User is allowed to change properties, so after property change,
|
||||
* location object is not in consistent state.
|
||||
*
|
||||
* Properties are synced with the following precedence order:
|
||||
*
|
||||
* - `$location.href`
|
||||
* - `$location.hash`
|
||||
* - everything else
|
||||
*
|
||||
* Keep in mind that if the following code is executed:
|
||||
*
|
||||
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
||||
*
|
||||
* immediately afterwards all other properties are still the old ones...
|
||||
*
|
||||
* This method checks the changes and update location to the consistent state
|
||||
*/
|
||||
function sync() {
|
||||
if (!equals(location, lastLocation)) {
|
||||
if (location.href != lastLocation.href) {
|
||||
update(location.href);
|
||||
} else {
|
||||
if (location.hash != lastLocation.hash) {
|
||||
var hash = parseHash(location.hash);
|
||||
updateHash(hash.hashPath, hash.hashSearch);
|
||||
} else {
|
||||
location.hash = composeHash(location);
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compose href string from a location object
|
||||
*
|
||||
* @param {Object} loc The location object with all properties
|
||||
* @return {string} Composed href
|
||||
*/
|
||||
function composeHref(loc) {
|
||||
var url = toKeyValue(loc.search);
|
||||
var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? null : loc.port);
|
||||
|
||||
return loc.protocol + '://' + loc.host +
|
||||
(port ? ':' + port : '') + loc.path +
|
||||
(url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose hash string from location object
|
||||
*
|
||||
* @param {Object} loc Object with hashPath and hashSearch properties
|
||||
* @return {string} Hash string
|
||||
*/
|
||||
function composeHash(loc) {
|
||||
var hashSearch = toKeyValue(loc.hashSearch);
|
||||
//TODO: temporary fix for issue #158
|
||||
return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') +
|
||||
(hashSearch ? '?' + hashSearch : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse href string into location object
|
||||
*
|
||||
* @param {string} href
|
||||
* @return {Object} The location object
|
||||
*/
|
||||
function parseHref(href) {
|
||||
var loc = {};
|
||||
var match = URL_MATCH.exec(href);
|
||||
|
||||
if (match) {
|
||||
loc.href = href.replace(/#$/, '');
|
||||
loc.protocol = match[1];
|
||||
loc.host = match[3] || '';
|
||||
loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || null;
|
||||
loc.path = match[6] || '';
|
||||
loc.search = parseKeyValue(match[8]);
|
||||
loc.hash = match[10] || '';
|
||||
|
||||
extend(loc, parseHash(loc.hash));
|
||||
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
|
||||
hashPrefix);
|
||||
}
|
||||
|
||||
return loc;
|
||||
// link rewriting
|
||||
var u = currentUrl,
|
||||
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
|
||||
|
||||
$document.bind('click', function(event) {
|
||||
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
|
||||
// currently we open nice url link and redirect then
|
||||
|
||||
if (uppercase(event.target.nodeName) != 'A' || event.ctrlKey || event.which == 2) return;
|
||||
|
||||
var elm = jqLite(event.target),
|
||||
href = elm.attr('href');
|
||||
|
||||
if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return;
|
||||
|
||||
// remove same domain from full url links (IE7 always returns full hrefs)
|
||||
href = href.replace(absUrlPrefix, '');
|
||||
|
||||
// link to different domain (or base path)
|
||||
if (href.substr(0, 4) == 'http') return;
|
||||
|
||||
// remove pathPrefix from absolute links
|
||||
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
|
||||
|
||||
currentUrl.url(href);
|
||||
scope.$apply();
|
||||
event.preventDefault();
|
||||
// hack to work around FF6 bug 684208 when scenario runner clicks on links
|
||||
window.angular['ff-684208-preventDefault'] = true;
|
||||
});
|
||||
} else {
|
||||
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hash string into object
|
||||
*
|
||||
* @param {string} hash
|
||||
*/
|
||||
function parseHash(hash) {
|
||||
var h = {};
|
||||
var match = HASH_MATCH.exec(hash);
|
||||
// rewrite hashbang url <> html5 url
|
||||
if (currentUrl.absUrl() != initUrl) {
|
||||
$browser.url(currentUrl.absUrl(), true);
|
||||
}
|
||||
|
||||
if (match) {
|
||||
h.hash = hash;
|
||||
h.hashPath = unescape(match[1] || '');
|
||||
h.hashSearch = parseKeyValue(match[3]);
|
||||
// update $location when $browser url changes
|
||||
$browser.onUrlChange(function(newUrl) {
|
||||
if (currentUrl.absUrl() != newUrl) {
|
||||
currentUrl.$$parse(newUrl);
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
|
||||
// update browser
|
||||
var changeCounter = 0;
|
||||
scope.$watch(function() {
|
||||
if ($browser.url() != currentUrl.absUrl()) {
|
||||
changeCounter++;
|
||||
scope.$evalAsync(function() {
|
||||
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
|
||||
currentUrl.$$replace = false;
|
||||
});
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}, ['$browser']);
|
||||
return changeCounter;
|
||||
});
|
||||
|
||||
return currentUrl;
|
||||
}, ['$browser', '$sniffer', '$locationConfig', '$document']);
|
||||
|
||||
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: false,
|
||||
hashPrefix: ''
|
||||
};
|
||||
});
|
||||
|
||||
+14
-7
@@ -15,13 +15,20 @@
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<p>Reload this page with open console, enter text and hit the log button...</p>
|
||||
Message:
|
||||
<input type="text" name="message" value="Hello World!"/>
|
||||
<button ng:click="$log.log(message)">log</button>
|
||||
<button ng:click="$log.warn(message)">warn</button>
|
||||
<button ng:click="$log.info(message)">info</button>
|
||||
<button ng:click="$log.error(message)">error</button>
|
||||
<script>
|
||||
function LogCtrl($log) {
|
||||
this.$log = $log;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="LogCtrl">
|
||||
<p>Reload this page with open console, enter text and hit the log button...</p>
|
||||
Message:
|
||||
<input type="text" name="message" value="Hello World!"/>
|
||||
<button ng:click="$log.log(message)">log</button>
|
||||
<button ng:click="$log.warn(message)">warn</button>
|
||||
<button ng:click="$log.info(message)">info</button>
|
||||
<button ng:click="$log.error(message)">error</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
|
||||
+34
-35
@@ -11,7 +11,7 @@
|
||||
* @property {Array.<Object>} routes Array of all configured routes.
|
||||
*
|
||||
* @description
|
||||
* Watches `$location.hashPath` and tries to map the hash to an existing route
|
||||
* Watches `$location.url()` and tries to map the path to an existing route
|
||||
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
|
||||
*
|
||||
* The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view}
|
||||
@@ -20,7 +20,6 @@
|
||||
* @example
|
||||
This example shows how changing the URL hash causes the <tt>$route</tt>
|
||||
to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial.
|
||||
Try changing the URL in the input box to see changes.
|
||||
|
||||
<doc:example>
|
||||
<doc:source jsfiddle="false">
|
||||
@@ -51,7 +50,7 @@
|
||||
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
|
||||
<a href="#/Book/Gatsby">Gatsby</a> |
|
||||
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
|
||||
$location.hashPath: <input type="text" name="$location.hashPath" size="80" />
|
||||
<pre>$location.path() = {{$location.path()}}</pre>
|
||||
<pre>$route.current.template = {{$route.current.template}}</pre>
|
||||
<pre>$route.current.params = {{$route.current.params}}</pre>
|
||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
|
||||
@@ -120,7 +119,7 @@ angularServiceInject('$route', function($location, $routeParams) {
|
||||
parentScope = this,
|
||||
rootScope = this,
|
||||
dirty = 0,
|
||||
allowReload = true,
|
||||
forceReload = false,
|
||||
$route = {
|
||||
routes: routes,
|
||||
|
||||
@@ -159,21 +158,20 @@ angularServiceInject('$route', function($location, $routeParams) {
|
||||
* {@link angular.widget.ng:view ng:view} or
|
||||
* {@link angular.widget.ng:include ng:include} widgets.
|
||||
* - `redirectTo` – {(string|function())=} – value to update
|
||||
* {@link angular.service.$location $location} hash with and trigger route redirection.
|
||||
* {@link angular.service.$location $location} path with and trigger route redirection.
|
||||
*
|
||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Object.<string>}` - route parameters extracted from the current
|
||||
* `$location.hashPath` by applying the current route template.
|
||||
* - `{string}` - current `$location.hash`
|
||||
* - `{string}` - current `$location.hashPath`
|
||||
* - `{string}` - current `$location.hashSearch`
|
||||
* `$location.path()` by applying the current route template.
|
||||
* - `{string}` - current `$location.path()`
|
||||
* - `{Object}` - current `$location.search()`
|
||||
*
|
||||
* The custom `redirectTo` function is expected to return a string which will be used
|
||||
* to update `$location.hash`.
|
||||
* to update `$location.path()` and `$location.search()`.
|
||||
*
|
||||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when $location.hashSearch
|
||||
* changes.
|
||||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
|
||||
* changes.
|
||||
*
|
||||
* If the option is set to false and url in the browser changes, then
|
||||
* $routeUpdate event is emited on the current route scope. You can use this event to
|
||||
@@ -190,11 +188,10 @@ angularServiceInject('$route', function($location, $routeParams) {
|
||||
* @description
|
||||
* Adds a new route definition to the `$route` service.
|
||||
*/
|
||||
when:function (path, route) {
|
||||
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
|
||||
when: function (path, route) {
|
||||
var routeDef = routes[path];
|
||||
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
|
||||
if (route) extend(routeDef, route); //TODO(im): what the heck? merge two route definitions?
|
||||
if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions?
|
||||
dirty++;
|
||||
return routeDef;
|
||||
},
|
||||
@@ -227,61 +224,63 @@ angularServiceInject('$route', function($location, $routeParams) {
|
||||
*/
|
||||
reload: function() {
|
||||
dirty++;
|
||||
allowReload = false;
|
||||
forceReload = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.$watch(function(){ return dirty + $location.hash; }, updateRoute);
|
||||
this.$watch(function() { return dirty + $location.url(); }, updateRoute);
|
||||
|
||||
return $route;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
function switchRouteMatcher(on, when, dstName) {
|
||||
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
|
||||
function switchRouteMatcher(on, when) {
|
||||
// TODO(i): this code is convoluted and inefficient, we should construct the route matching
|
||||
// regex only once and then reuse it
|
||||
var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
|
||||
params = [],
|
||||
dst = {};
|
||||
forEach(when.split(/\W/), function(param){
|
||||
forEach(when.split(/\W/), function(param) {
|
||||
if (param) {
|
||||
var paramRegExp = new RegExp(":" + param + "([\\W])");
|
||||
if (regex.match(paramRegExp)) {
|
||||
regex = regex.replace(paramRegExp, "([^\/]*)$1");
|
||||
regex = regex.replace(paramRegExp, "([^\\/]*)$1");
|
||||
params.push(param);
|
||||
}
|
||||
}
|
||||
});
|
||||
var match = on.match(new RegExp(regex));
|
||||
if (match) {
|
||||
forEach(params, function(name, index){
|
||||
forEach(params, function(name, index) {
|
||||
dst[name] = match[index + 1];
|
||||
});
|
||||
if (dstName) this.$set(dstName, dst);
|
||||
}
|
||||
return match ? dst : null;
|
||||
}
|
||||
|
||||
function updateRoute(){
|
||||
function updateRoute() {
|
||||
var next = parseRoute(),
|
||||
last = $route.current;
|
||||
|
||||
if (next && last && next.$route === last.$route
|
||||
&& equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && allowReload) {
|
||||
&& equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
|
||||
$route.current = next;
|
||||
copy(next.params, $routeParams);
|
||||
last.scope && last.scope.$emit('$routeUpdate');
|
||||
} else {
|
||||
allowReload = true;
|
||||
forceReload = false;
|
||||
rootScope.$broadcast('$beforeRouteChange', next, last);
|
||||
last && last.scope && last.scope.$destroy();
|
||||
$route.current = next;
|
||||
if (next) {
|
||||
if (next.redirectTo) {
|
||||
$location.update(isString(next.redirectTo)
|
||||
? {hashSearch: next.params, hashPath: interpolate(next.redirectTo, next.params)}
|
||||
: {hash: next.redirectTo(next.pathParams,
|
||||
$location.hash, $location.hashPath, $location.hashSearch)});
|
||||
if (isString(next.redirectTo)) {
|
||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
|
||||
.replace();
|
||||
} else {
|
||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
|
||||
.replace();
|
||||
}
|
||||
} else {
|
||||
copy(next.params, $routeParams);
|
||||
next.scope = parentScope.$new(next.controller);
|
||||
@@ -295,13 +294,13 @@ angularServiceInject('$route', function($location, $routeParams) {
|
||||
/**
|
||||
* @returns the current active route, by matching it against the URL
|
||||
*/
|
||||
function parseRoute(){
|
||||
function parseRoute() {
|
||||
// Match a route
|
||||
var params, match;
|
||||
forEach(routes, function(route, path) {
|
||||
if (!match && (params = matcher($location.hashPath, path))) {
|
||||
if (!match && (params = matcher($location.path(), path))) {
|
||||
match = inherit(route, {
|
||||
params: extend({}, $location.hashSearch, params),
|
||||
params: extend({}, $location.search(), params),
|
||||
pathParams: params});
|
||||
match.$route = route;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
*
|
||||
* @description
|
||||
* Current set of route parameters. The route parameters are a combination of the
|
||||
* {@link angular.service.$location $location} `hashSearch`, and `path`. The `path` parameters
|
||||
* {@link angular.service.$location $location} `search()`, and `path()`. The `path` parameters
|
||||
* are extracted when the {@link angular.service.$route $route} path is matched.
|
||||
*
|
||||
* In case of parameter name collision, `path` params take precedence over `hashSearch` params.
|
||||
* In case of parameter name collision, `path` params take precedence over `search` params.
|
||||
*
|
||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged
|
||||
* (but its properties will likely change) even when a route change occurs.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$sniffer
|
||||
* @requires $window
|
||||
*
|
||||
* @property {boolean} history Does the browser support html5 history api ?
|
||||
* @property {boolean} hashchange Does the browser support hashchange event ?
|
||||
*
|
||||
* @description
|
||||
* This is very simple implementation of testing browser's features.
|
||||
*/
|
||||
angularServiceInject('$sniffer', function($window) {
|
||||
if ($window.Modernizr) return $window.Modernizr;
|
||||
|
||||
return {
|
||||
history: !!($window.history && $window.history.pushState),
|
||||
hashchange: 'onhashchange' in $window &&
|
||||
// IE8 compatible mode lies
|
||||
(!$window.document.documentMode || $window.document.documentMode > 7)
|
||||
};
|
||||
}, ['$window']);
|
||||
+5
-5
@@ -140,8 +140,8 @@
|
||||
<input type="text" name="url" value="index.html" size="80"/>
|
||||
<button ng:click="fetch()">fetch</button><br>
|
||||
<button ng:click="updateModel('GET', 'index.html')">Sample GET</button>
|
||||
<button ng:click="updateModel('JSON', 'https://www.googleapis.com/buzz/v1/activities/googlebuzz/@self?alt=json&callback=JSON_CALLBACK')">Sample JSONP (Buzz API)</button>
|
||||
<button ng:click="updateModel('JSON', 'https://www.invalid_JSONP_request.com&callback=JSON_CALLBACK')">Invalid JSONP</button>
|
||||
<button ng:click="updateModel('JSON', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
|
||||
<button ng:click="updateModel('JSON', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
|
||||
<pre>code={{code}}</pre>
|
||||
<pre>response={{response}}</pre>
|
||||
</div>
|
||||
@@ -154,11 +154,11 @@
|
||||
expect(binding('response')).toMatch(/angularjs.org/);
|
||||
});
|
||||
|
||||
it('should make JSONP request to the Buzz API', function() {
|
||||
element(':button:contains("Buzz API")').click();
|
||||
it('should make JSONP request to the angularjs.org', function() {
|
||||
element(':button:contains("Sample JSONP")').click();
|
||||
element(':button:contains("fetch")').click();
|
||||
expect(binding('code')).toBe('code=200');
|
||||
expect(binding('response')).toMatch(/buzz-feed/);
|
||||
expect(binding('response')).toMatch(/Super Hero!/);
|
||||
});
|
||||
|
||||
it('should make JSONP request to invalid URL and invoke the error handler',
|
||||
|
||||
+29
-29
@@ -61,10 +61,10 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate non ssn', function(){
|
||||
var textBox = element('.doc-example-live :input');
|
||||
expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(textBox.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(textBox.val()).toEqual('123-45-6789');
|
||||
input('ssn').enter('123-45-67890');
|
||||
expect(textBox.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(textBox.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -102,17 +102,17 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate number', function(){
|
||||
var n1 = element('.doc-example-live :input[name=n1]');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n1').enter('1.x');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
var n2 = element('.doc-example-live :input[name=n2]');
|
||||
expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n2.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n2').enter('9');
|
||||
expect(n2.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n2.prop('className')).toMatch(/ng-validation-error/);
|
||||
var n3 = element('.doc-example-live :input[name=n3]');
|
||||
expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n3.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n3').enter('201');
|
||||
expect(n3.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n3.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -156,17 +156,17 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate integer', function(){
|
||||
var n1 = element('.doc-example-live :input[name=n1]');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n1').enter('1.1');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
var n2 = element('.doc-example-live :input[name=n2]');
|
||||
expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n2.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n2').enter('10.1');
|
||||
expect(n2.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n2.prop('className')).toMatch(/ng-validation-error/);
|
||||
var n3 = element('.doc-example-live :input[name=n3]');
|
||||
expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n3.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('n3').enter('100.1');
|
||||
expect(n3.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n3.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -200,9 +200,9 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate date', function(){
|
||||
var n1 = element('.doc-example-live :input');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('text').enter('123/123/123');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -238,9 +238,9 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate email', function(){
|
||||
var n1 = element('.doc-example-live :input');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('text').enter('a@b.c');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -272,9 +272,9 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate phone', function(){
|
||||
var n1 = element('.doc-example-live :input');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('text').enter('+12345678');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -310,9 +310,9 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate url', function(){
|
||||
var n1 = element('.doc-example-live :input');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('text').enter('abc://server/path');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -345,9 +345,9 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should invalidate json', function(){
|
||||
var n1 = element('.doc-example-live :input');
|
||||
expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('json').enter('{name}');
|
||||
expect(n1.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(n1.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -417,13 +417,13 @@ extend(angularValidator, {
|
||||
<doc:scenario>
|
||||
it('should change color in delayed way', function(){
|
||||
var textBox = element('.doc-example-live :input');
|
||||
expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(textBox.prop('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
expect(textBox.prop('className')).not().toMatch(/ng-validation-error/);
|
||||
input('text').enter('X');
|
||||
expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/);
|
||||
expect(textBox.prop('className')).toMatch(/ng-input-indicator-wait/);
|
||||
sleep(.6);
|
||||
expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
expect(textBox.attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(textBox.prop('className')).not().toMatch(/ng-input-indicator-wait/);
|
||||
expect(textBox.prop('className')).toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
+24
-14
@@ -244,11 +244,11 @@ function compileFormatter(expr) {
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:validate', function(){
|
||||
expect(element('.doc-example-live :input:last').attr('className')).
|
||||
expect(element('.doc-example-live :input:last').prop('className')).
|
||||
toMatch(/ng-validation-error/);
|
||||
|
||||
input('value').enter('123');
|
||||
expect(element('.doc-example-live :input:last').attr('className')).
|
||||
expect(element('.doc-example-live :input:last').prop('className')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -276,9 +276,11 @@ function compileFormatter(expr) {
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:required', function(){
|
||||
expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/);
|
||||
expect(element('.doc-example-live :input').prop('className')).
|
||||
toMatch(/ng-validation-error/);
|
||||
input('value').enter('123');
|
||||
expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/);
|
||||
expect(element('.doc-example-live :input').prop('className')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -716,7 +718,8 @@ angularWidget('select', function(element){
|
||||
// optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
|
||||
var optionGroupsCache = [[{element: selectElement, label:''}]],
|
||||
scope = this,
|
||||
model = modelAccessor(scope, element);
|
||||
model = modelAccessor(scope, element),
|
||||
inChangeEvent;
|
||||
|
||||
// find existing special options
|
||||
forEach(selectElement.children(), function(option){
|
||||
@@ -733,6 +736,12 @@ angularWidget('select', function(element){
|
||||
tempScope = scope.$new(),
|
||||
value, optionElement, index, groupIndex, length, groupLength;
|
||||
|
||||
// let's set a flag that the current model change is due to a change event.
|
||||
// the default action of option selection will cause the appropriate option element to be
|
||||
// deselected and another one to be selected - there is no need for us to be updating the DOM
|
||||
// in this case.
|
||||
inChangeEvent = true;
|
||||
|
||||
try {
|
||||
if (isMultiselect) {
|
||||
value = [];
|
||||
@@ -762,12 +771,13 @@ angularWidget('select', function(element){
|
||||
}
|
||||
}
|
||||
if (isDefined(value) && model.get() !== value) {
|
||||
onChange(scope);
|
||||
model.set(value);
|
||||
onChange(scope);
|
||||
}
|
||||
scope.$root.$apply();
|
||||
} finally {
|
||||
tempScope = null; // TODO(misko): needs to be $destroy
|
||||
inChangeEvent = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -886,8 +896,8 @@ angularWidget('select', function(element){
|
||||
if (existingOption.id !== option.id) {
|
||||
lastElement.val(existingOption.id = option.id);
|
||||
}
|
||||
if (existingOption.selected !== option.selected) {
|
||||
lastElement.attr('selected', option.selected);
|
||||
if (!inChangeEvent && existingOption.selected !== option.selected) {
|
||||
lastElement.prop('selected', (existingOption.selected = option.selected));
|
||||
}
|
||||
} else {
|
||||
// grow elements
|
||||
@@ -902,7 +912,7 @@ angularWidget('select', function(element){
|
||||
element: element,
|
||||
label: option.label,
|
||||
id: option.id,
|
||||
checked: option.selected
|
||||
selected: option.selected
|
||||
});
|
||||
if (lastElement) {
|
||||
lastElement.after(element);
|
||||
@@ -1380,10 +1390,10 @@ angularWidget("@ng:non-bindable", noop);
|
||||
function MyCtrl($route) {
|
||||
$route.when('/overview',
|
||||
{ controller: OverviewCtrl,
|
||||
template: 'guide/dev_guide.overview.html'});
|
||||
template: 'partials/guide/dev_guide.overview.html'});
|
||||
$route.when('/bootstrap',
|
||||
{ controller: BootstrapCtrl,
|
||||
template: 'guide/dev_guide.bootstrap.auto_bootstrap.html'});
|
||||
template: 'partials/guide/dev_guide.bootstrap.auto_bootstrap.html'});
|
||||
};
|
||||
MyCtrl.$inject = ['$route'];
|
||||
|
||||
@@ -1391,9 +1401,9 @@ angularWidget("@ng:non-bindable", noop);
|
||||
function OverviewCtrl(){}
|
||||
</script>
|
||||
<div ng:controller="MyCtrl">
|
||||
<a href="#/overview">overview</a> |
|
||||
<a href="#/bootstrap">bootstrap</a> |
|
||||
<a href="#/undefined">undefined</a>
|
||||
<a href="overview">overview</a> |
|
||||
<a href="bootstrap">bootstrap</a> |
|
||||
<a href="undefined">undefined</a>
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
+1
-1
@@ -597,7 +597,7 @@ describe('angular', function(){
|
||||
describe('nodeName_', function() {
|
||||
it('should correctly detect node name with "namespace" when xmlns is defined', function() {
|
||||
var div = jqLite('<div xmlns:ngtest="http://angularjs.org/">' +
|
||||
'<ngtest:foo ngtest:attr="bar"></ng:test>' +
|
||||
'<ngtest:foo ngtest:attr="bar"></ngtest:foo>' +
|
||||
'</div>')[0];
|
||||
expect(nodeName_(div.childNodes[0])).toBe('NGTEST:FOO');
|
||||
expect(div.childNodes[0].getAttribute('ngtest:attr')).toBe('bar');
|
||||
|
||||
@@ -175,6 +175,26 @@ describe('api', function(){
|
||||
expect(angular.Array.limitTo(items, null)).toEqual([]);
|
||||
expect(angular.Array.limitTo(items, undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should return an empty array when input is not Array type', function() {
|
||||
expect(angular.Array.limitTo('bogus', 1)).toEqual([]);
|
||||
expect(angular.Array.limitTo(null, 1)).toEqual([]);
|
||||
expect(angular.Array.limitTo(undefined, 1)).toEqual([]);
|
||||
expect(angular.Array.limitTo(null, 1)).toEqual([]);
|
||||
expect(angular.Array.limitTo(undefined, 1)).toEqual([]);
|
||||
expect(angular.Array.limitTo({}, 1)).toEqual([]);
|
||||
});
|
||||
|
||||
|
||||
it('should return a copy of input array if X is exceeds array length', function () {
|
||||
expect(angular.Array.limitTo(items, 19)).toEqual(items);
|
||||
expect(angular.Array.limitTo(items, '9')).toEqual(items);
|
||||
expect(angular.Array.limitTo(items, -9)).toEqual(items);
|
||||
expect(angular.Array.limitTo(items, '-9')).toEqual(items);
|
||||
|
||||
expect(angular.Array.limitTo(items, 9)).not.toBe(items);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
+244
-115
@@ -1,36 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
describe('browser', function(){
|
||||
function MockWindow() {
|
||||
var events = {};
|
||||
var timeouts = this.timeouts = [];
|
||||
|
||||
var browser, fakeWindow, xhr, logs, scripts, removedScripts, setTimeoutQueue;
|
||||
this.setTimeout = function(fn) {
|
||||
return timeouts.push(fn) - 1;
|
||||
};
|
||||
|
||||
function fakeSetTimeout(fn) {
|
||||
return setTimeoutQueue.push(fn) - 1; //return position in the queue
|
||||
}
|
||||
this.clearTimeout = function(id) {
|
||||
timeouts[id] = noop;
|
||||
};
|
||||
|
||||
function fakeClearTimeout(deferId) {
|
||||
setTimeoutQueue[deferId] = noop; //replace fn with noop to preserve other deferId indexes
|
||||
}
|
||||
this.setTimeout.flush = function() {
|
||||
var length = timeouts.length;
|
||||
while (length-- > 0) timeouts.shift()();
|
||||
};
|
||||
|
||||
fakeSetTimeout.flush = function() {
|
||||
var currentTimeoutQueue = setTimeoutQueue;
|
||||
setTimeoutQueue = [];
|
||||
forEach(currentTimeoutQueue, function(fn) {
|
||||
fn();
|
||||
this.addEventListener = function(name, listener) {
|
||||
if (isUndefined(events[name])) events[name] = [];
|
||||
events[name].push(listener);
|
||||
};
|
||||
|
||||
this.attachEvent = function(name, listener) {
|
||||
this.addEventListener(name.substr(2), listener);
|
||||
};
|
||||
|
||||
this.removeEventListener = noop;
|
||||
this.detachEvent = noop;
|
||||
|
||||
this.fire = function(name) {
|
||||
forEach(events[name], function(fn) {
|
||||
fn({type: name}); // type to make jQuery happy
|
||||
});
|
||||
};
|
||||
|
||||
this.location = {
|
||||
href: 'http://server',
|
||||
replace: noop
|
||||
};
|
||||
|
||||
this.history = {
|
||||
replaceState: noop,
|
||||
pushState: noop
|
||||
};
|
||||
}
|
||||
|
||||
describe('browser', function(){
|
||||
|
||||
var browser, fakeWindow, xhr, logs, scripts, removedScripts, sniffer;
|
||||
|
||||
beforeEach(function(){
|
||||
setTimeoutQueue = [];
|
||||
scripts = [];
|
||||
removedScripts = [];
|
||||
xhr = null;
|
||||
fakeWindow = {
|
||||
location: {href:"http://server"},
|
||||
setTimeout: fakeSetTimeout,
|
||||
clearTimeout: fakeClearTimeout
|
||||
};
|
||||
sniffer = {history: true, hashchange: true};
|
||||
fakeWindow = new MockWindow();
|
||||
|
||||
var fakeBody = [{appendChild: function(node){scripts.push(node);},
|
||||
removeChild: function(node){removedScripts.push(node);}}];
|
||||
@@ -59,7 +84,7 @@ describe('browser', function(){
|
||||
error: function() { logs.error.push(slice.call(arguments)); }};
|
||||
|
||||
browser = new Browser(fakeWindow, jqLite(window.document), fakeBody, FakeXhr,
|
||||
fakeLog);
|
||||
fakeLog, sniffer);
|
||||
});
|
||||
|
||||
it('should contain cookie cruncher', function() {
|
||||
@@ -202,22 +227,24 @@ describe('browser', function(){
|
||||
|
||||
describe('defer', function() {
|
||||
it('should execute fn asynchroniously via setTimeout', function() {
|
||||
var counter = 0;
|
||||
browser.defer(function() {counter++;});
|
||||
expect(counter).toBe(0);
|
||||
var callback = jasmine.createSpy('deferred');
|
||||
|
||||
fakeSetTimeout.flush();
|
||||
expect(counter).toBe(1);
|
||||
browser.defer(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should update outstandingRequests counter', function() {
|
||||
var callback = jasmine.createSpy('callback');
|
||||
var callback = jasmine.createSpy('deferred');
|
||||
|
||||
browser.defer(callback);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
fakeSetTimeout.flush();
|
||||
expect(callback).toHaveBeenCalled();
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
@@ -241,7 +268,7 @@ describe('browser', function(){
|
||||
expect(log).toEqual([]);
|
||||
browser.defer.cancel(deferId1);
|
||||
browser.defer.cancel(deferId3);
|
||||
fakeSetTimeout.flush();
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(log).toEqual(['ok']);
|
||||
});
|
||||
});
|
||||
@@ -458,21 +485,21 @@ describe('browser', function(){
|
||||
browser.addPollFn(function(){log+='a';});
|
||||
browser.addPollFn(function(){log+='b';});
|
||||
expect(log).toEqual('');
|
||||
fakeSetTimeout.flush();
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(log).toEqual('ab');
|
||||
fakeSetTimeout.flush();
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(log).toEqual('abab');
|
||||
});
|
||||
|
||||
it('should startPoller', function(){
|
||||
expect(setTimeoutQueue.length).toEqual(0);
|
||||
expect(fakeWindow.timeouts.length).toEqual(0);
|
||||
|
||||
browser.addPollFn(function(){});
|
||||
expect(setTimeoutQueue.length).toEqual(1);
|
||||
expect(fakeWindow.timeouts.length).toEqual(1);
|
||||
|
||||
//should remain 1 as it is the check fn
|
||||
browser.addPollFn(function(){});
|
||||
expect(setTimeoutQueue.length).toEqual(1);
|
||||
expect(fakeWindow.timeouts.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return fn that was passed into addPollFn', function() {
|
||||
@@ -482,96 +509,162 @@ describe('browser', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('url', function() {
|
||||
var pushState, replaceState, locationReplace;
|
||||
|
||||
describe('url api', function() {
|
||||
it('should use $browser poller to detect url changes when onhashchange event is unsupported',
|
||||
function() {
|
||||
|
||||
fakeWindow = {
|
||||
location: {href:"http://server"},
|
||||
document: {},
|
||||
setTimeout: fakeSetTimeout
|
||||
};
|
||||
|
||||
browser = new Browser(fakeWindow, {}, {});
|
||||
browser.startPoller = function() {};
|
||||
|
||||
var events = [];
|
||||
|
||||
browser.onHashChange(function() {
|
||||
events.push('x');
|
||||
});
|
||||
|
||||
fakeWindow.location.href = "http://server/#newHash";
|
||||
expect(events).toEqual([]);
|
||||
fakeSetTimeout.flush();
|
||||
expect(events).toEqual(['x']);
|
||||
|
||||
//don't do anything if url hasn't changed
|
||||
events = [];
|
||||
fakeSetTimeout.flush();
|
||||
expect(events).toEqual([]);
|
||||
beforeEach(function() {
|
||||
pushState = spyOn(fakeWindow.history, 'pushState');
|
||||
replaceState = spyOn(fakeWindow.history, 'replaceState');
|
||||
locationReplace = spyOn(fakeWindow.location, 'replace');
|
||||
});
|
||||
|
||||
it('should return current location.href', function() {
|
||||
fakeWindow.location.href = 'http://test.com';
|
||||
expect(browser.url()).toEqual('http://test.com');
|
||||
|
||||
it('should use onhashchange events to detect url changes when supported by browser',
|
||||
function() {
|
||||
|
||||
var onHashChngListener;
|
||||
|
||||
fakeWindow = {location: {href:"http://server"},
|
||||
addEventListener: function(type, listener) {
|
||||
expect(type).toEqual('hashchange');
|
||||
onHashChngListener = listener;
|
||||
},
|
||||
attachEvent: function(type, listener) {
|
||||
expect(type).toEqual('onhashchange');
|
||||
onHashChngListener = listener;
|
||||
},
|
||||
removeEventListener: angular.noop,
|
||||
detachEvent: angular.noop,
|
||||
document: {}
|
||||
};
|
||||
fakeWindow.onhashchange = true;
|
||||
|
||||
browser = new Browser(fakeWindow, {}, {});
|
||||
|
||||
var events = [],
|
||||
event = {type: "hashchange"};
|
||||
|
||||
browser.onHashChange(function(e) {
|
||||
events.push(e);
|
||||
});
|
||||
|
||||
expect(events).toEqual([]);
|
||||
onHashChngListener(event);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].originalEvent || events[0]).toBe(event); // please jQuery and jqLite
|
||||
|
||||
// clean up the jqLite cache so that the global afterEach doesn't complain
|
||||
if (!jQuery) {
|
||||
jqLite(fakeWindow).dealoc();
|
||||
}
|
||||
fakeWindow.location.href = 'https://another.com';
|
||||
expect(browser.url()).toEqual('https://another.com');
|
||||
});
|
||||
|
||||
// asynchronous test
|
||||
it('should fire onHashChange when location.hash change', function() {
|
||||
var callback = jasmine.createSpy('onHashChange');
|
||||
browser = new Browser(window, {}, {});
|
||||
browser.onHashChange(callback);
|
||||
it('should use history.pushState when available', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://new.org');
|
||||
|
||||
window.location.hash = 'new-hash';
|
||||
browser.addPollFn(function() {});
|
||||
expect(pushState).toHaveBeenCalledOnce();
|
||||
expect(pushState.argsForCall[0][2]).toEqual('http://new.org');
|
||||
|
||||
waitsFor(function() {
|
||||
return callback.callCount;
|
||||
}, 'onHashChange callback to be called', 1000);
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
});
|
||||
|
||||
runs(function() {
|
||||
if (!jQuery) jqLite(window).dealoc();
|
||||
window.location.hash = '';
|
||||
});
|
||||
it('should use history.replaceState when available', function() {
|
||||
sniffer.history = true;
|
||||
browser.url('http://new.org', true);
|
||||
|
||||
expect(replaceState).toHaveBeenCalledOnce();
|
||||
expect(replaceState.argsForCall[0][2]).toEqual('http://new.org');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
});
|
||||
|
||||
it('should set location.href when pushState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org');
|
||||
|
||||
expect(fakeWindow.location.href).toEqual('http://new.org');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(locationReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should use location.replace when history.replaceState not available', function() {
|
||||
sniffer.history = false;
|
||||
browser.url('http://new.org', true);
|
||||
|
||||
expect(locationReplace).toHaveBeenCalledWith('http://new.org');
|
||||
|
||||
expect(pushState).not.toHaveBeenCalled();
|
||||
expect(replaceState).not.toHaveBeenCalled();
|
||||
expect(fakeWindow.location.href).toEqual('http://server');
|
||||
});
|
||||
|
||||
it('should return $browser to allow chaining', function() {
|
||||
expect(browser.url('http://any.com')).toBe(browser);
|
||||
});
|
||||
});
|
||||
|
||||
describe('urlChange', function() {
|
||||
var callback;
|
||||
|
||||
beforeEach(function() {
|
||||
callback = jasmine.createSpy('onUrlChange');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (!jQuery) jqLite(fakeWindow).dealoc();
|
||||
});
|
||||
|
||||
it('should return registered callback', function() {
|
||||
expect(browser.onUrlChange(callback)).toBe(callback);
|
||||
});
|
||||
|
||||
it('should forward popstate event with new url when history supported', function() {
|
||||
sniffer.history = true;
|
||||
browser.onUrlChange(callback);
|
||||
fakeWindow.location.href = 'http://server/new';
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
expect(callback).toHaveBeenCalledWith('http://server/new');
|
||||
|
||||
fakeWindow.fire('hashchange');
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should forward only popstate event when both history and hashchange supported', function() {
|
||||
sniffer.history = true;
|
||||
sniffer.hashchange = true;
|
||||
browser.onUrlChange(callback);
|
||||
fakeWindow.location.href = 'http://server/new';
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
expect(callback).toHaveBeenCalledWith('http://server/new');
|
||||
|
||||
fakeWindow.fire('hashchange');
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should forward hashchange event with new url when only hashchange supported', function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = true;
|
||||
browser.onUrlChange(callback);
|
||||
fakeWindow.location.href = 'http://server/new';
|
||||
|
||||
fakeWindow.fire('hashchange');
|
||||
expect(callback).toHaveBeenCalledWith('http://server/new');
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should use polling when neither history nor hashchange supported', function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = false;
|
||||
browser.onUrlChange(callback);
|
||||
|
||||
fakeWindow.location.href = 'http://server.new';
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).toHaveBeenCalledWith('http://server.new');
|
||||
|
||||
fakeWindow.fire('popstate');
|
||||
fakeWindow.fire('hashchange');
|
||||
expect(callback).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should not fire urlChange if changed by browser.url method (polling)', function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = false;
|
||||
browser.onUrlChange(callback);
|
||||
browser.url('http://new.com');
|
||||
|
||||
fakeWindow.setTimeout.flush();
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not fire urlChange if changed by browser.url method (hashchange)', function() {
|
||||
sniffer.history = false;
|
||||
sniffer.hashchange = true;
|
||||
browser.onUrlChange(callback);
|
||||
browser.url('http://new.com');
|
||||
|
||||
fakeWindow.fire('hashchange');
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -598,4 +691,40 @@ describe('browser', function(){
|
||||
expect(script).toBe(scripts[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('baseHref', function() {
|
||||
var jqDocHead;
|
||||
|
||||
function setDocumentBaseHrefTo(href) {
|
||||
clearDocumentBaseHref();
|
||||
jqDocHead.append('<base href="' + href +'" />');
|
||||
}
|
||||
|
||||
function clearDocumentBaseHref() {
|
||||
jqDocHead.find('base').remove();
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
jqDocHead = jqLite(document).find('head');
|
||||
});
|
||||
|
||||
afterEach(clearDocumentBaseHref);
|
||||
|
||||
it('should return value from <base href>', function() {
|
||||
setDocumentBaseHrefTo('/base/path/');
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
});
|
||||
|
||||
it('should return undefined if no <base href>', function() {
|
||||
expect(browser.baseHref()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should remove domain from <base href>', function() {
|
||||
setDocumentBaseHrefTo('http://host.com/base/path/');
|
||||
expect(browser.baseHref()).toEqual('/base/path/');
|
||||
|
||||
setDocumentBaseHrefTo('http://host.com/base/path/index.html');
|
||||
expect(browser.baseHref()).toEqual('/base/path/index.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('compiler', function(){
|
||||
|
||||
|
||||
it('should observe scope', function(){
|
||||
scope = compile('<span observe="name">');
|
||||
scope = compile('<span observe="name"></span>');
|
||||
expect(log).toEqual("");
|
||||
scope.$digest();
|
||||
scope.name = 'misko';
|
||||
|
||||
+22
-6
@@ -83,23 +83,39 @@ describe('filter', function() {
|
||||
});
|
||||
|
||||
describe('currency', function() {
|
||||
it('should do basic currency filtering', function() {
|
||||
var html = jqLite('<span/>');
|
||||
var context = createScope();
|
||||
context.$element = html;
|
||||
var currency = bind(context, filter.currency);
|
||||
var currency, html, context;
|
||||
|
||||
beforeEach(function() {
|
||||
html = jqLite('<span></span>');
|
||||
context = createScope();
|
||||
context.$element = html;
|
||||
currency = bind(context, filter.currency);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(context);
|
||||
});
|
||||
|
||||
|
||||
it('should do basic currency filtering', function() {
|
||||
expect(currency(0)).toEqual('$0.00');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
expect(currency(-999)).toEqual('($999.00)');
|
||||
expect(html.hasClass('ng-format-negative')).toBeTruthy();
|
||||
expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
});
|
||||
|
||||
dealoc(context);
|
||||
|
||||
it('should return empty string for non-numbers', function() {
|
||||
expect(currency()).toBe('');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
expect(currency('abc')).toBe('');
|
||||
expect(html.hasClass('ng-format-negative')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('number', function() {
|
||||
var context, number;
|
||||
|
||||
|
||||
+19
-3
@@ -391,7 +391,7 @@ describe('parser', function() {
|
||||
expect(scope.a).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should allow assignment after array dereference', function(){
|
||||
it('should allow assignment after array dereference', function() {
|
||||
scope = angular.scope();
|
||||
scope.obj = [{}];
|
||||
scope.$eval('obj[0].name=1');
|
||||
@@ -399,14 +399,30 @@ describe('parser', function() {
|
||||
expect(scope.obj[0].name).toEqual(1);
|
||||
});
|
||||
|
||||
describe('formatter', function(){
|
||||
it('should short-circuit AND operator', function() {
|
||||
var scope = angular.scope();
|
||||
scope.run = function() {
|
||||
throw "IT SHOULD NOT HAVE RUN";
|
||||
};
|
||||
expect(scope.$eval('false && run()')).toBe(false);
|
||||
});
|
||||
|
||||
it('should short-circuit OR operator', function() {
|
||||
var scope = angular.scope();
|
||||
scope.run = function() {
|
||||
throw "IT SHOULD NOT HAVE RUN";
|
||||
};
|
||||
expect(scope.$eval('true || run()')).toBe(true);
|
||||
});
|
||||
|
||||
describe('formatter', function() {
|
||||
it('should return no argument function', function() {
|
||||
var noop = parser('noop').formatter()();
|
||||
expect(noop.format(null, 'abc')).toEqual('abc');
|
||||
expect(noop.parse(null, '123')).toEqual('123');
|
||||
});
|
||||
|
||||
it('should delegate arguments', function(){
|
||||
it('should delegate arguments', function() {
|
||||
angularFormatter.myArgs = {
|
||||
parse: function(a, b){ return [a, b]; },
|
||||
format: function(a, b){ return [a, b]; }
|
||||
|
||||
@@ -75,6 +75,15 @@ describe("resource", function() {
|
||||
nakedExpect(item).toEqual({id:'abc'});
|
||||
});
|
||||
|
||||
it("should build resource with action default param overriding default param", function(){
|
||||
xhr.expectGET('/Customer/123').respond({id:'abc'});
|
||||
var TypeItem = resource.route('/:type/:typeId', {type: 'Order'},
|
||||
{get: {method: 'GET', params: {type: 'Customer'}}});
|
||||
var item = TypeItem.get({typeId:123});
|
||||
xhr.flush();
|
||||
nakedExpect(item).toEqual({id:'abc'});
|
||||
});
|
||||
|
||||
it("should create resource", function(){
|
||||
xhr.expectPOST('/CreditCard', {name:'misko'}).respond({id:123, name:'misko'});
|
||||
|
||||
|
||||
@@ -32,17 +32,4 @@ describe("ScenarioSpec: Compilation", function(){
|
||||
expect(jqLite(scope.$element).text()).toEqual('123');
|
||||
});
|
||||
});
|
||||
|
||||
describe("configuration", function(){
|
||||
it("should take location object", function(){
|
||||
var url = "http://server/#?book=moby";
|
||||
scope = angular.compile("<div>{{$location}}</div>")();
|
||||
var $location = scope.$service('$location');
|
||||
var $browser = scope.$service('$browser');
|
||||
expect($location.hashSearch.book).toBeUndefined();
|
||||
$browser.setUrl(url);
|
||||
$browser.poll();
|
||||
expect($location.hashSearch.book).toEqual('moby');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+29
-29
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
describe("directive", function(){
|
||||
describe("directive", function() {
|
||||
|
||||
var compile, model, element;
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("directive", function(){
|
||||
expect(scope.a).toEqual(123);
|
||||
});
|
||||
|
||||
describe('ng:bind', function(){
|
||||
describe('ng:bind', function() {
|
||||
it('should set text', function() {
|
||||
var scope = compile('<div ng:bind="a"></div>');
|
||||
expect(element.text()).toEqual('');
|
||||
@@ -74,7 +74,7 @@ describe("directive", function(){
|
||||
});
|
||||
|
||||
|
||||
it('should suppress rendering of falsy values', function(){
|
||||
it('should suppress rendering of falsy values', function() {
|
||||
var scope = compile('<div>{{ null }}{{ undefined }}{{ "" }}-{{ 0 }}{{ false }}</div>');
|
||||
scope.$digest();
|
||||
expect(scope.$element.text()).toEqual('-0false');
|
||||
@@ -82,7 +82,7 @@ describe("directive", function(){
|
||||
|
||||
});
|
||||
|
||||
describe('ng:bind-template', function(){
|
||||
describe('ng:bind-template', function() {
|
||||
it('should ng:bind-template', function() {
|
||||
var scope = compile('<div ng:bind-template="Hello {{name}}!"></div>');
|
||||
scope.name = 'Misko';
|
||||
@@ -91,9 +91,9 @@ describe("directive", function(){
|
||||
expect(element.text()).toEqual('Hello Misko!');
|
||||
});
|
||||
|
||||
it('should have $element set to current bind element', function(){
|
||||
it('should have $element set to current bind element', function() {
|
||||
var innerText;
|
||||
angularFilter.myFilter = function(text){
|
||||
angularFilter.myFilter = function(text) {
|
||||
innerText = innerText || this.$element.text();
|
||||
return text;
|
||||
};
|
||||
@@ -105,22 +105,22 @@ describe("directive", function(){
|
||||
|
||||
});
|
||||
|
||||
describe('ng:bind-attr', function(){
|
||||
it('should bind attributes', function(){
|
||||
describe('ng:bind-attr', function() {
|
||||
it('should bind attributes', function() {
|
||||
var scope = compile('<div ng:bind-attr="{src:\'http://localhost/mysrc\', alt:\'myalt\'}"/>');
|
||||
scope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://localhost/mysrc');
|
||||
expect(element.attr('alt')).toEqual('myalt');
|
||||
});
|
||||
|
||||
it('should not pretty print JSON in attributes', function(){
|
||||
it('should not pretty print JSON in attributes', function() {
|
||||
var scope = compile('<img alt="{{ {a:1} }}"/>');
|
||||
scope.$digest();
|
||||
expect(element.attr('alt')).toEqual('{"a":1}');
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove special attributes on false', function(){
|
||||
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);
|
||||
@@ -138,7 +138,7 @@ describe("directive", function(){
|
||||
});
|
||||
|
||||
describe('ng:click', function(){
|
||||
it('should get called on a click', function(){
|
||||
it('should get called on a click', function() {
|
||||
var scope = compile('<div ng:click="clicked = true"></div>');
|
||||
scope.$digest();
|
||||
expect(scope.clicked).toBeFalsy();
|
||||
@@ -254,7 +254,7 @@ describe("directive", function(){
|
||||
scope.$digest();
|
||||
scope.dynCls = 'foo';
|
||||
scope.$digest();
|
||||
expect(element.attr('class')).toBe('ui-panel ui-selected ng-directive foo');
|
||||
expect(element[0].className).toBe('ui-panel ui-selected ng-directive foo');
|
||||
});
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ describe("directive", function(){
|
||||
var scope = compile('<div class="panel bar" ng:class="dynCls"></div>');
|
||||
scope.dynCls = 'panel';
|
||||
scope.$digest();
|
||||
expect(element.attr('class')).toBe('panel bar ng-directive');
|
||||
expect(element[0].className).toBe('panel bar ng-directive');
|
||||
});
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ describe("directive", function(){
|
||||
scope.$digest();
|
||||
scope.dynCls = 'window';
|
||||
scope.$digest();
|
||||
expect(element.attr('class')).toBe('bar ng-directive window');
|
||||
expect(element[0].className).toBe('bar ng-directive window');
|
||||
});
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ describe("directive", function(){
|
||||
element.addClass('foo');
|
||||
scope.dynCls = '';
|
||||
scope.$digest();
|
||||
expect(element.attr('class')).toBe('ng-directive');
|
||||
expect(element[0].className).toBe('ng-directive');
|
||||
});
|
||||
|
||||
|
||||
@@ -291,12 +291,12 @@ describe("directive", function(){
|
||||
var scope = compile('<div ng:class="dynCls"></div>');
|
||||
scope.dynCls = [undefined, null];
|
||||
scope.$digest();
|
||||
expect(element.attr('class')).toBe('ng-directive');
|
||||
expect(element[0].className).toBe('ng-directive');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should ng:class odd/even', function(){
|
||||
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.$digest();
|
||||
var e1 = jqLite(element[0].childNodes[1]);
|
||||
@@ -351,8 +351,8 @@ describe("directive", function(){
|
||||
});
|
||||
|
||||
|
||||
describe('ng:style', function(){
|
||||
it('should set', function(){
|
||||
describe('ng:style', function() {
|
||||
it('should set', function() {
|
||||
var scope = compile('<div ng:style="{height: \'40px\'}"></div>');
|
||||
scope.$digest();
|
||||
expect(element.css('height')).toEqual('40px');
|
||||
@@ -364,7 +364,7 @@ describe("directive", function(){
|
||||
expect(element.hasClass('ng-exception')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should preserve and remove previous style', function(){
|
||||
it('should preserve and remove previous style', function() {
|
||||
var scope = compile('<div style="height: 10px;" ng:style="myStyle"></div>');
|
||||
scope.$digest();
|
||||
expect(getStyle(element)).toEqual({height: '10px'});
|
||||
@@ -385,7 +385,7 @@ describe("directive", function(){
|
||||
|
||||
|
||||
describe('ng:show', function() {
|
||||
it('should show and hide an element', function(){
|
||||
it('should show and hide an element', function() {
|
||||
var element = jqLite('<div ng:show="exp"></div>'),
|
||||
scope = compile(element);
|
||||
|
||||
@@ -420,13 +420,13 @@ describe("directive", function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('ng:controller', function(){
|
||||
describe('ng:controller', function() {
|
||||
|
||||
var temp;
|
||||
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
temp = window.temp = {};
|
||||
temp.Greeter = function(){
|
||||
temp.Greeter = function() {
|
||||
this.$root.greeter = this;
|
||||
this.greeting = 'hello';
|
||||
this.suffix = '!';
|
||||
@@ -438,18 +438,18 @@ describe("directive", function(){
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(function(){
|
||||
afterEach(function() {
|
||||
window.temp = undefined;
|
||||
});
|
||||
|
||||
it('should bind', function(){
|
||||
it('should bind', function() {
|
||||
var scope = compile('<div ng:controller="temp.Greeter"></div>');
|
||||
expect(scope.greeter.greeting).toEqual('hello');
|
||||
expect(scope.greeter.greet('misko')).toEqual('hello misko!');
|
||||
});
|
||||
|
||||
it('should support nested controllers', function(){
|
||||
temp.ChildGreeter = function(){
|
||||
it('should support nested controllers', function() {
|
||||
temp.ChildGreeter = function() {
|
||||
this.greeting = 'hey';
|
||||
this.$root.childGreeter = this;
|
||||
};
|
||||
@@ -478,7 +478,7 @@ describe("directive", function(){
|
||||
|
||||
expect(element.attr('ng:cloak')).toBe('');
|
||||
|
||||
angular.compile(element)
|
||||
angular.compile(element);
|
||||
|
||||
expect(element.attr('ng:cloak')).toBeUndefined();
|
||||
});
|
||||
|
||||
+222
-32
@@ -39,6 +39,11 @@ describe('jqLite', function(){
|
||||
});
|
||||
|
||||
|
||||
it('should be jqLite when jqLiteMode is on, otherwise jQuery', function() {
|
||||
expect(jqLite).toBe(_jqLiteMode ? jqLiteWrap : _jQuery);
|
||||
});
|
||||
|
||||
|
||||
describe('construction', function(){
|
||||
it('should allow construction with text node', function(){
|
||||
var text = a.firstChild;
|
||||
@@ -150,32 +155,55 @@ describe('jqLite', function(){
|
||||
expect(jqLite(b).attr('prop')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should read special attributes as boolean', function(){
|
||||
it('should read special attributes as strings', function(){
|
||||
var select = jqLite('<select>');
|
||||
expect(select.attr('multiple')).toEqual(false);
|
||||
expect(jqLite('<select multiple>').attr('multiple')).toEqual(true);
|
||||
expect(jqLite('<select multiple="">').attr('multiple')).toEqual(true);
|
||||
expect(jqLite('<select multiple="x">').attr('multiple')).toEqual(true);
|
||||
expect(select.attr('multiple')).toBeUndefined();
|
||||
expect(jqLite('<select multiple>').attr('multiple')).toBe('multiple');
|
||||
expect(jqLite('<select multiple="">').attr('multiple')).toBe('multiple');
|
||||
expect(jqLite('<select multiple="x">').attr('multiple')).toBe('multiple');
|
||||
|
||||
select.attr('multiple', false);
|
||||
expect(select.attr('multiple')).toEqual(false);
|
||||
expect(select.attr('multiple')).toBeUndefined();
|
||||
|
||||
select.attr('multiple', true);
|
||||
expect(select.attr('multiple')).toEqual(true);
|
||||
expect(select.attr('multiple')).toBe('multiple');
|
||||
});
|
||||
|
||||
it('should return undefined for non-existing attributes', function() {
|
||||
var elm = jqLite('<div class="any">a</div>');
|
||||
expect(elm.attr('non-existing')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should special-case "class" attribute', function() {
|
||||
// stupid IE9 returns null for element.getAttribute('class') when element has ng:class attr
|
||||
var elm = jqLite('<div class=" any " ng:class="dynCls">a</div>');
|
||||
expect(elm.attr('class')).toBe(' any ');
|
||||
|
||||
elm.attr('class', 'foo bar');
|
||||
expect(elm.attr('class')).toBe('foo bar');
|
||||
describe('prop', function() {
|
||||
it('should read element property', function() {
|
||||
var elm = jqLite('<div class="foo">a</div>');
|
||||
expect(elm.prop('className')).toBe('foo');
|
||||
});
|
||||
|
||||
it('should set element property to a value', function() {
|
||||
var elm = jqLite('<div class="foo">a</div>');
|
||||
elm.prop('className', 'bar');
|
||||
expect(elm[0].className).toBe('bar');
|
||||
expect(elm.prop('className')).toBe('bar');
|
||||
});
|
||||
|
||||
it('should set boolean element property', function() {
|
||||
var elm = jqLite('<input type="checkbox">');
|
||||
expect(elm.prop('checked')).toBe(false);
|
||||
|
||||
elm.prop('checked', true);
|
||||
expect(elm.prop('checked')).toBe(true);
|
||||
|
||||
elm.prop('checked', '');
|
||||
expect(elm.prop('checked')).toBe(false);
|
||||
|
||||
elm.prop('checked', 'lala');
|
||||
expect(elm.prop('checked')).toBe(true);
|
||||
|
||||
elm.prop('checked', null);
|
||||
expect(elm.prop('checked')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -297,34 +325,65 @@ describe('jqLite', function(){
|
||||
it('should set and read css', function(){
|
||||
var selector = jqLite([a, b]);
|
||||
|
||||
expect(selector.css('prop', 'value')).toEqual(selector);
|
||||
expect(jqLite(a).css('prop')).toEqual('value');
|
||||
expect(jqLite(b).css('prop')).toEqual('value');
|
||||
expect(selector.css('margin', '1px')).toEqual(selector);
|
||||
expect(jqLite(a).css('margin')).toEqual('1px');
|
||||
expect(jqLite(b).css('margin')).toEqual('1px');
|
||||
|
||||
expect(selector.css({'prop': 'new value'})).toEqual(selector);
|
||||
expect(jqLite(a).css('prop')).toEqual('new value');
|
||||
expect(jqLite(b).css('prop')).toEqual('new value');
|
||||
expect(selector.css({'margin': '2px'})).toEqual(selector);
|
||||
expect(jqLite(a).css('margin')).toEqual('2px');
|
||||
expect(jqLite(b).css('margin')).toEqual('2px');
|
||||
|
||||
jqLite(b).css({'prop': 'new value 2'});
|
||||
expect(jqLite(selector).css('prop')).toEqual('new value');
|
||||
expect(jqLite(b).css('prop')).toEqual('new value 2');
|
||||
jqLite(b).css({'margin': '3px'});
|
||||
expect(jqLite(selector).css('margin')).toEqual('2px');
|
||||
expect(jqLite(a).css('margin')).toEqual('2px');
|
||||
expect(jqLite(b).css('margin')).toEqual('3px');
|
||||
|
||||
selector.css('prop', null);
|
||||
expect(jqLite(a).css('prop')).toBeFalsy();
|
||||
expect(jqLite(b).css('prop')).toBeFalsy();
|
||||
selector.css('margin', '');
|
||||
if (msie <= 8) {
|
||||
expect(jqLite(a).css('margin')).toBe('auto');
|
||||
expect(jqLite(b).css('margin')).toBe('auto');
|
||||
} else {
|
||||
expect(jqLite(a).css('margin')).toBeFalsy();
|
||||
expect(jqLite(b).css('margin')).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should set a bunch of css properties specified via an object', function() {
|
||||
expect(jqLite(a).css('foo')).toBeFalsy();
|
||||
expect(jqLite(a).css('bar')).toBeFalsy();
|
||||
expect(jqLite(a).css('baz')).toBeFalsy();
|
||||
if (msie <= 8) {
|
||||
expect(jqLite(a).css('margin')).toBe('auto');
|
||||
expect(jqLite(a).css('padding')).toBe('0px');
|
||||
expect(jqLite(a).css('border')).toBeUndefined();
|
||||
} else {
|
||||
expect(jqLite(a).css('margin')).toBeFalsy();
|
||||
expect(jqLite(a).css('padding')).toBeFalsy();
|
||||
expect(jqLite(a).css('border')).toBeFalsy();
|
||||
}
|
||||
|
||||
jqLite(a).css({'foo': 'a', 'bar': 'b', 'baz': ''});
|
||||
jqLite(a).css({'margin': '1px', 'padding': '2px', 'border': ''});
|
||||
|
||||
expect(jqLite(a).css('foo')).toBe('a');
|
||||
expect(jqLite(a).css('bar')).toBe('b');
|
||||
expect(jqLite(a).css('baz')).toBeFalsy();
|
||||
expect(jqLite(a).css('margin')).toBe('1px');
|
||||
expect(jqLite(a).css('padding')).toBe('2px');
|
||||
expect(jqLite(a).css('border')).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should correctly handle dash-separated and camelCased properties', function() {
|
||||
var jqA = jqLite(a);
|
||||
|
||||
expect(jqA.css('z-index')).toBeOneOf('', 'auto');
|
||||
expect(jqA.css('zIndex')).toBeOneOf('', 'auto');
|
||||
|
||||
|
||||
jqA.css({'zIndex':5});
|
||||
|
||||
expect(jqA.css('z-index')).toBeOneOf('5', 5);
|
||||
expect(jqA.css('zIndex')).toBeOneOf('5', 5);
|
||||
|
||||
jqA.css({'z-index':7});
|
||||
|
||||
expect(jqA.css('z-index')).toBeOneOf('7', 7);
|
||||
expect(jqA.css('zIndex')).toBeOneOf('7', 7);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -443,6 +502,114 @@ describe('jqLite', function(){
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
});
|
||||
|
||||
it('should have event.isDefaultPrevented method', function() {
|
||||
jqLite(a).bind('click', function(e) {
|
||||
expect(function() {
|
||||
expect(e.isDefaultPrevented()).toBe(false);
|
||||
e.preventDefault();
|
||||
expect(e.isDefaultPrevented()).toBe(true);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('unbind', function() {
|
||||
it('should do nothing when no listener was registered with bound', function() {
|
||||
var aElem = jqLite(a);
|
||||
|
||||
aElem.unbind();
|
||||
aElem.unbind('click');
|
||||
aElem.unbind('click', function() {});
|
||||
});
|
||||
|
||||
|
||||
it('should deregister all listeners', function() {
|
||||
var aElem = jqLite(a),
|
||||
clickSpy = jasmine.createSpy('click'),
|
||||
mouseoverSpy = jasmine.createSpy('mouseover');
|
||||
|
||||
aElem.bind('click', clickSpy);
|
||||
aElem.bind('mouseover', mouseoverSpy);
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy).toHaveBeenCalledOnce();
|
||||
browserTrigger(a, 'mouseover');
|
||||
expect(mouseoverSpy).toHaveBeenCalledOnce();
|
||||
|
||||
clickSpy.reset();
|
||||
mouseoverSpy.reset();
|
||||
|
||||
aElem.unbind();
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
browserTrigger(a, 'mouseover');
|
||||
expect(mouseoverSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should deregister listeners for specific type', function() {
|
||||
var aElem = jqLite(a),
|
||||
clickSpy = jasmine.createSpy('click'),
|
||||
mouseoverSpy = jasmine.createSpy('mouseover');
|
||||
|
||||
aElem.bind('click', clickSpy);
|
||||
aElem.bind('mouseover', mouseoverSpy);
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy).toHaveBeenCalledOnce();
|
||||
browserTrigger(a, 'mouseover');
|
||||
expect(mouseoverSpy).toHaveBeenCalledOnce();
|
||||
|
||||
clickSpy.reset();
|
||||
mouseoverSpy.reset();
|
||||
|
||||
aElem.unbind('click');
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
browserTrigger(a, 'mouseover');
|
||||
expect(mouseoverSpy).toHaveBeenCalledOnce();
|
||||
|
||||
mouseoverSpy.reset();
|
||||
|
||||
aElem.unbind('mouseover');
|
||||
browserTrigger(a, 'mouseover');
|
||||
expect(mouseoverSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should deregister specific listener', function() {
|
||||
var aElem = jqLite(a),
|
||||
clickSpy1 = jasmine.createSpy('click1'),
|
||||
clickSpy2 = jasmine.createSpy('click2');
|
||||
|
||||
aElem.bind('click', clickSpy1);
|
||||
aElem.bind('click', clickSpy2);
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy1).toHaveBeenCalledOnce();
|
||||
expect(clickSpy2).toHaveBeenCalledOnce();
|
||||
|
||||
clickSpy1.reset();
|
||||
clickSpy2.reset();
|
||||
|
||||
aElem.unbind('click', clickSpy1);
|
||||
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy1).not.toHaveBeenCalled();
|
||||
expect(clickSpy2).toHaveBeenCalledOnce();
|
||||
|
||||
clickSpy2.reset();
|
||||
|
||||
aElem.unbind('click', clickSpy2);
|
||||
browserTrigger(a, 'click');
|
||||
expect(clickSpy2).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -599,4 +766,27 @@ describe('jqLite', function(){
|
||||
expect(element.find('span').eq(20).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('camelCase', function() {
|
||||
|
||||
it('should leave non-dashed strings alone', function() {
|
||||
expect(camelCase('foo')).toBe('foo');
|
||||
expect(camelCase('')).toBe('');
|
||||
expect(camelCase('fooBar')).toBe('fooBar');
|
||||
});
|
||||
|
||||
|
||||
it('should covert dash-separated strings to camelCase', function() {
|
||||
expect(camelCase('foo-bar')).toBe('fooBar');
|
||||
expect(camelCase('foo-bar-baz')).toBe('fooBarBaz');
|
||||
});
|
||||
|
||||
|
||||
it('should covert browser specific css properties', function() {
|
||||
expect(camelCase('-moz-foo-bar')).toBe('MozFooBar');
|
||||
expect(camelCase('-webkit-foo-bar')).toBe('webkitFooBar');
|
||||
expect(camelCase('-webkit-foo-bar')).toBe('webkitFooBar');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+2
-1
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
var _jQuery = jQuery;
|
||||
var _jQuery = jQuery,
|
||||
_jqLiteMode = false;
|
||||
|
||||
Vendored
+2
-1
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
var _jQuery = jQuery.noConflict(true);
|
||||
var _jQuery = jQuery.noConflict(true),
|
||||
_jqLiteMode = true;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
<!-- include source files here... -->
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery.js"></script>
|
||||
<script type="text/javascript"> var _jQuery = $;</script>
|
||||
|
||||
<script type="text/javascript" src="../src/Angular.js"></script>
|
||||
|
||||
+7
-1
@@ -63,6 +63,11 @@ describe("markups", function(){
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (element) element.remove();
|
||||
});
|
||||
|
||||
|
||||
it('should populate value attribute on OPTION', function(){
|
||||
compile('<select name="x"><option>abc</option></select>');
|
||||
expect(element).toHaveValue('abc');
|
||||
@@ -114,6 +119,7 @@ describe("markups", function(){
|
||||
|
||||
it('should bind selected', function() {
|
||||
compile('<select><option value=""></option><option ng:selected="{{isSelected}}">Greetings!</option></select>');
|
||||
jqLite(document.body).append(element)
|
||||
scope.isSelected=false;
|
||||
scope.$digest();
|
||||
expect(element.children()[1].selected).toBeFalsy();
|
||||
@@ -160,7 +166,7 @@ describe("markups", function(){
|
||||
expect(sortedHtml(element)).toEqual('<div ng:bind-attr="{"' + name +'":"some"}"></div>');
|
||||
dealoc(element);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('should Parse Text With No Bindings', function(){
|
||||
var parts = parseBindings("a");
|
||||
|
||||
@@ -250,6 +250,20 @@ describe("angular.scenario.dsl", function() {
|
||||
expect($window.location).toMatch(/#foo$/);
|
||||
});
|
||||
|
||||
it('should not navigate if click event was cancelled', function() {
|
||||
var initLocation = $window.location,
|
||||
elm = jqLite('<a href="#foo"></a>');
|
||||
|
||||
doc.append(elm);
|
||||
elm.bind('click', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
$root.dsl.element('a').click();
|
||||
expect($window.location).toBe(initLocation);
|
||||
dealoc(elm);
|
||||
});
|
||||
|
||||
it('should count matching elements', function() {
|
||||
doc.append('<span></span><span></span>');
|
||||
$root.dsl.element('span').count();
|
||||
@@ -273,6 +287,18 @@ describe("angular.scenario.dsl", function() {
|
||||
expect(doc.find('div').attr('class')).toEqual('bam');
|
||||
});
|
||||
|
||||
it('should get property', function() {
|
||||
doc.append('<div id="test" class="foo"></div>');
|
||||
$root.dsl.element('#test').prop('className');
|
||||
expect($root.futureResult).toEqual('foo');
|
||||
});
|
||||
|
||||
it('should set property', function() {
|
||||
doc.append('<div id="test" class="foo"></div>');
|
||||
$root.dsl.element('#test').prop('className', 'bam');
|
||||
expect(doc.find('div').prop('className')).toEqual('bam');
|
||||
});
|
||||
|
||||
it('should get css', function() {
|
||||
doc.append('<div id="test" style="height: 30px"></div>');
|
||||
$root.dsl.element('#test').css('height');
|
||||
@@ -490,15 +516,15 @@ describe("angular.scenario.dsl", function() {
|
||||
it('should toggle checkbox state', function() {
|
||||
doc.append('<input type="checkbox" name="test.input" checked>');
|
||||
expect(_jQuery('input[name="test.input"]').
|
||||
attr('checked')).toBeTruthy();
|
||||
prop('checked')).toBe(true);
|
||||
var chain = $root.dsl.input('test.input');
|
||||
chain.check();
|
||||
expect(_jQuery('input[name="test.input"]').
|
||||
attr('checked')).toBeFalsy();
|
||||
prop('checked')).toBe(false);
|
||||
$window.angular.reset();
|
||||
chain.check();
|
||||
expect(_jQuery('input[name="test.input"]').
|
||||
attr('checked')).toBeTruthy();
|
||||
prop('checked')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error if checkbox did not match', function() {
|
||||
@@ -513,17 +539,17 @@ describe("angular.scenario.dsl", function() {
|
||||
'<input type="radio" name="0@test.input" value="bar" checked="checked">'
|
||||
);
|
||||
// HACK! We don't know why this is sometimes false on chrome
|
||||
_jQuery('input[name="0@test.input"][value="bar"]').attr('checked', true);
|
||||
_jQuery('input[name="0@test.input"][value="bar"]').prop('checked', true);
|
||||
expect(_jQuery('input[name="0@test.input"][value="bar"]').
|
||||
attr('checked')).toBeTruthy();
|
||||
prop('checked')).toBe(true);
|
||||
expect(_jQuery('input[name="0@test.input"][value="foo"]').
|
||||
attr('checked')).toBeFalsy();
|
||||
prop('checked')).toBe(false);
|
||||
var chain = $root.dsl.input('test.input');
|
||||
chain.select('foo');
|
||||
expect(_jQuery('input[name="0@test.input"][value="bar"]').
|
||||
attr('checked')).toBeFalsy();
|
||||
prop('checked')).toBe(false);
|
||||
expect(_jQuery('input[name="0@test.input"][value="foo"]').
|
||||
attr('checked')).toBeTruthy();
|
||||
prop('checked')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return error if radio button did not match', function() {
|
||||
|
||||
@@ -43,6 +43,5 @@ describe('$locale', function() {
|
||||
expect($locale.pluralCat(0)).toBe('other');
|
||||
expect($locale.pluralCat(2)).toBe('other');
|
||||
expect($locale.pluralCat(1)).toBe('one');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+585
-227
@@ -1,251 +1,469 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Create jasmine.Spy on given method, but ignore calls without arguments
|
||||
* This is helpful when need to spy only setter methods and ignore getters
|
||||
*/
|
||||
function spyOnlyCallsWithArgs(obj, method) {
|
||||
var spy = spyOn(obj, method);
|
||||
obj[method] = function() {
|
||||
if (arguments.length) return spy.apply(this, arguments);
|
||||
return spy.originalValue.apply(this);
|
||||
};
|
||||
return spy;
|
||||
}
|
||||
|
||||
|
||||
describe('$location', function() {
|
||||
var scope, $location, $browser;
|
||||
var url;
|
||||
|
||||
afterEach(function() {
|
||||
// link rewriting used in html5 mode on legacy browsers binds to document.onClick, so we need
|
||||
// to clean this up after each test.
|
||||
jqLite(document).unbind('click');
|
||||
});
|
||||
|
||||
describe('NewUrl', function() {
|
||||
beforeEach(function() {
|
||||
url = new LocationUrl('http://www.domain.com:9877/path/b?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
|
||||
it('should provide common getters', function() {
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#hash');
|
||||
expect(url.protocol()).toBe('http');
|
||||
expect(url.host()).toBe('www.domain.com');
|
||||
expect(url.port()).toBe(9877);
|
||||
expect(url.path()).toBe('/path/b');
|
||||
expect(url.search()).toEqual({search: 'a', b: 'c', d: true});
|
||||
expect(url.hash()).toBe('hash');
|
||||
expect(url.url()).toBe('/path/b?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
|
||||
it('path() should change path', function() {
|
||||
url.path('/new/path');
|
||||
expect(url.path()).toBe('/new/path');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash');
|
||||
});
|
||||
|
||||
|
||||
it('search() should accept string', function() {
|
||||
url.search('x=y&c');
|
||||
expect(url.search()).toEqual({x: 'y', c: true});
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?x=y&c#hash');
|
||||
});
|
||||
|
||||
|
||||
it('search() should accept object', function() {
|
||||
url.search({one: 1, two: true});
|
||||
expect(url.search()).toEqual({one: 1, two: true});
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two#hash');
|
||||
});
|
||||
|
||||
|
||||
it('search() should change single parameter', function() {
|
||||
url.search({id: 'old', preserved: true});
|
||||
url.search('id', 'new');
|
||||
|
||||
expect(url.search()).toEqual({id: 'new', preserved: true});
|
||||
});
|
||||
|
||||
|
||||
it('search() should remove single parameter', function() {
|
||||
url.search({id: 'old', preserved: true});
|
||||
url.search('id', null);
|
||||
|
||||
expect(url.search()).toEqual({preserved: true});
|
||||
});
|
||||
|
||||
|
||||
it('hash() should change hash fragment', function() {
|
||||
url.hash('new-hash');
|
||||
expect(url.hash()).toBe('new-hash');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#new-hash');
|
||||
});
|
||||
|
||||
|
||||
it('url() should change the path, search and hash', function() {
|
||||
url.url('/some/path?a=b&c=d#hhh');
|
||||
expect(url.url()).toBe('/some/path?a=b&c=d#hhh');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/some/path?a=b&c=d#hhh');
|
||||
expect(url.path()).toBe('/some/path');
|
||||
expect(url.search()).toEqual({a: 'b', c: 'd'});
|
||||
expect(url.hash()).toBe('hhh');
|
||||
});
|
||||
|
||||
|
||||
it('replace should set $$replace flag and return itself', function() {
|
||||
expect(url.$$replace).toBe(false);
|
||||
|
||||
url.replace();
|
||||
expect(url.$$replace).toBe(true);
|
||||
expect(url.replace()).toBe(url);
|
||||
});
|
||||
|
||||
|
||||
it('should parse new url', function() {
|
||||
url = new LocationUrl('http://host.com/base');
|
||||
expect(url.path()).toBe('/base');
|
||||
|
||||
url = new LocationUrl('http://host.com/base#');
|
||||
expect(url.path()).toBe('/base');
|
||||
});
|
||||
|
||||
|
||||
it('should prefix path with forward-slash', function() {
|
||||
url = new LocationUrl('http://server/a');
|
||||
url.path('b');
|
||||
|
||||
expect(url.path()).toBe('/b');
|
||||
expect(url.absUrl()).toBe('http://server/b');
|
||||
});
|
||||
|
||||
|
||||
it('should set path to forward-slash when empty', function() {
|
||||
url = new LocationUrl('http://server');
|
||||
expect(url.path()).toBe('/');
|
||||
expect(url.absUrl()).toBe('http://server/');
|
||||
});
|
||||
|
||||
|
||||
it('setters should return Url object to allow chaining', function() {
|
||||
expect(url.path('/any')).toBe(url);
|
||||
expect(url.search('')).toBe(url);
|
||||
expect(url.hash('aaa')).toBe(url);
|
||||
expect(url.url('/some')).toBe(url);
|
||||
});
|
||||
|
||||
|
||||
it('should not preserve old properties when parsing new url', function() {
|
||||
url.$$parse('http://www.domain.com:9877/a');
|
||||
|
||||
expect(url.path()).toBe('/a');
|
||||
expect(url.search()).toEqual({});
|
||||
expect(url.hash()).toBe('');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/a');
|
||||
});
|
||||
|
||||
|
||||
it('should prepend path with basePath', function() {
|
||||
url = new LocationUrl('http://server/base/abc?a', '/base');
|
||||
expect(url.path()).toBe('/abc');
|
||||
expect(url.search()).toEqual({a: true});
|
||||
|
||||
url.path('/new/path');
|
||||
expect(url.absUrl()).toBe('http://server/base/new/path?a');
|
||||
});
|
||||
|
||||
|
||||
it('should throw error when invalid url given', function() {
|
||||
url = new LocationUrl('http://server.org/base/abc', '/base');
|
||||
|
||||
expect(function() {
|
||||
url.$$parse('http://server.org/path#/path');
|
||||
}).toThrow('Invalid url "http://server.org/path#/path", missing path prefix "/base" !');
|
||||
});
|
||||
|
||||
|
||||
describe('encoding', function() {
|
||||
|
||||
it('should encode special characters', function() {
|
||||
url.path('/a <>#');
|
||||
url.search({'i j': '<>#'});
|
||||
url.hash('<>#');
|
||||
|
||||
expect(url.path()).toBe('/a <>#');
|
||||
expect(url.search()).toEqual({'i j': '<>#'});
|
||||
expect(url.hash()).toBe('<>#');
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23');
|
||||
});
|
||||
|
||||
|
||||
it('should not encode !$:@', function() {
|
||||
url.path('/!$:@');
|
||||
url.search('');
|
||||
url.hash('!$:@');
|
||||
|
||||
expect(url.absUrl()).toBe('http://www.domain.com:9877/!$:@#!$:@');
|
||||
});
|
||||
|
||||
|
||||
it('should decode special characters', function() {
|
||||
url = new LocationUrl('http://host.com/a%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23');
|
||||
expect(url.path()).toBe('/a <>#');
|
||||
expect(url.search()).toEqual({'i j': '<>#'});
|
||||
expect(url.hash()).toBe('x <>#');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('HashbangUrl', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
url = new LocationHashbangUrl('http://www.server.org:1234/base#!/path?a=b&c#hash', '!');
|
||||
});
|
||||
|
||||
|
||||
it('should parse hashband url into path and search', function() {
|
||||
expect(url.protocol()).toBe('http');
|
||||
expect(url.host()).toBe('www.server.org');
|
||||
expect(url.port()).toBe(1234);
|
||||
expect(url.path()).toBe('/path');
|
||||
expect(url.search()).toEqual({a: 'b', c: true});
|
||||
expect(url.hash()).toBe('hash');
|
||||
});
|
||||
|
||||
|
||||
it('absUrl() should return hashbang url', function() {
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/path?a=b&c#hash');
|
||||
|
||||
url.path('/new/path');
|
||||
url.search({one: 1});
|
||||
url.hash('hhh');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/new/path?one=1#hhh');
|
||||
});
|
||||
|
||||
|
||||
it('should preserve query params in base', function() {
|
||||
url = new LocationHashbangUrl('http://www.server.org:1234/base?base=param#/path?a=b&c#hash', '');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base?base=param#/path?a=b&c#hash');
|
||||
|
||||
url.path('/new/path');
|
||||
url.search({one: 1});
|
||||
url.hash('hhh');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base?base=param#/new/path?one=1#hhh');
|
||||
});
|
||||
|
||||
|
||||
it('should prefix path with forward-slash', function() {
|
||||
url = new LocationHashbangUrl('http://host.com/base#path', '');
|
||||
expect(url.path()).toBe('/path');
|
||||
expect(url.absUrl()).toBe('http://host.com/base#/path');
|
||||
|
||||
url.path('wrong');
|
||||
expect(url.path()).toBe('/wrong');
|
||||
expect(url.absUrl()).toBe('http://host.com/base#/wrong');
|
||||
});
|
||||
|
||||
|
||||
it('should set path to forward-slash when empty', function() {
|
||||
url = new LocationHashbangUrl('http://server/base#!', '!');
|
||||
url.path('aaa');
|
||||
|
||||
expect(url.path()).toBe('/aaa');
|
||||
expect(url.absUrl()).toBe('http://server/base#!/aaa');
|
||||
});
|
||||
|
||||
|
||||
it('should not preserve old properties when parsing new url', function() {
|
||||
url.$$parse('http://www.server.org:1234/base#!/');
|
||||
|
||||
expect(url.path()).toBe('/');
|
||||
expect(url.search()).toEqual({});
|
||||
expect(url.hash()).toBe('');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/');
|
||||
});
|
||||
|
||||
|
||||
it('should throw error when invalid url given', function() {
|
||||
expect(function() {
|
||||
url.$$parse('http://server.org/path#/path');
|
||||
}).toThrow('Invalid url "http://server.org/path#/path", missing hash prefix "!" !');
|
||||
});
|
||||
|
||||
|
||||
describe('encoding', function() {
|
||||
|
||||
it('should encode special characters', function() {
|
||||
url.path('/a <>#');
|
||||
url.search({'i j': '<>#'});
|
||||
url.hash('<>#');
|
||||
|
||||
expect(url.path()).toBe('/a <>#');
|
||||
expect(url.search()).toEqual({'i j': '<>#'});
|
||||
expect(url.hash()).toBe('<>#');
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23');
|
||||
});
|
||||
|
||||
|
||||
it('should not encode !$:@', function() {
|
||||
url.path('/!$:@');
|
||||
url.search('');
|
||||
url.hash('!$:@');
|
||||
|
||||
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!/!$:@#!$:@');
|
||||
});
|
||||
|
||||
|
||||
it('should decode special characters', function() {
|
||||
url = new LocationHashbangUrl('http://host.com/a#/%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23', '');
|
||||
expect(url.path()).toBe('/ <>#');
|
||||
expect(url.search()).toEqual({'i j': '<>#'});
|
||||
expect(url.hash()).toBe('x <>#');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var $browser, $location, scope;
|
||||
|
||||
function init(url, html5Mode, basePath, hashPrefix, supportHistory) {
|
||||
scope = angular.scope(null, {
|
||||
$locationConfig: {html5Mode: html5Mode, hashPrefix: hashPrefix},
|
||||
$sniffer: {history: supportHistory}});
|
||||
|
||||
beforeEach(function(){
|
||||
scope = angular.scope();
|
||||
$location = scope.$service('$location');
|
||||
$browser = scope.$service('$browser');
|
||||
});
|
||||
$browser.url(url);
|
||||
$browser.$$baseHref = basePath;
|
||||
$location = scope.$service('$location');
|
||||
}
|
||||
|
||||
function dealocRootElement() {
|
||||
dealoc(scope.$service('$document'));
|
||||
}
|
||||
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(scope);
|
||||
});
|
||||
describe('wiring', function() {
|
||||
|
||||
|
||||
it("should update location object immediately when update is called", function() {
|
||||
var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=';
|
||||
$location.update(href);
|
||||
expect($location.href).toEqual(href);
|
||||
expect($location.protocol).toEqual("http");
|
||||
expect($location.host).toEqual("host");
|
||||
expect($location.port).toEqual("123");
|
||||
expect($location.path).toEqual("/p/a/t/h.html");
|
||||
expect($location.search).toEqual({query:'value'});
|
||||
expect($location.hash).toEqual('path?key=value&flag&key2=');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
expect($location.hashSearch).toEqual({key: 'value', flag: true, key2: ''});
|
||||
});
|
||||
|
||||
|
||||
it('should update location when browser url changed', function() {
|
||||
var origUrl = $location.href;
|
||||
expect(origUrl).toEqual($browser.getUrl());
|
||||
|
||||
var newUrl = 'http://somenew/url#foo';
|
||||
$browser.setUrl(newUrl);
|
||||
$browser.poll();
|
||||
expect($location.href).toEqual(newUrl);
|
||||
});
|
||||
|
||||
|
||||
it('should update browser at the end of $eval', function() {
|
||||
var origBrowserUrl = $browser.getUrl();
|
||||
$location.update('http://www.angularjs.org/');
|
||||
$location.update({path: '/a/b'});
|
||||
expect($location.href).toEqual('http://www.angularjs.org/a/b');
|
||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b');
|
||||
$location.path = '/c';
|
||||
scope.$digest();
|
||||
expect($browser.getUrl()).toEqual('http://www.angularjs.org/c');
|
||||
});
|
||||
|
||||
|
||||
it('should update hashPath and hashSearch on hash update', function(){
|
||||
$location.update('http://server/#path?a=b');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
expect($location.hashSearch).toEqual({a:'b'});
|
||||
|
||||
$location.update({hash: ''});
|
||||
expect($location.hashPath).toEqual('');
|
||||
expect($location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
|
||||
it('should update hash on hashPath or hashSearch update', function() {
|
||||
$location.update('http://server/#path?a=b');
|
||||
scope.$digest();
|
||||
$location.update({hashPath: '', hashSearch: {}});
|
||||
|
||||
expect($location.hash).toEqual('');
|
||||
});
|
||||
|
||||
|
||||
it('should update hashPath and hashSearch on $location.hash change upon eval', function(){
|
||||
$location.update('http://server/#path?a=b');
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '';
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toEqual('http://server/');
|
||||
expect($location.hashPath).toEqual('');
|
||||
expect($location.hashSearch).toEqual({});
|
||||
});
|
||||
|
||||
|
||||
it('should update hash on $location.hashPath or $location.hashSearch change upon eval',
|
||||
function() {
|
||||
$location.update('http://server/#path?a=b');
|
||||
expect($location.href).toEqual('http://server/#path?a=b');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
expect($location.hashSearch).toEqual({a:'b'});
|
||||
|
||||
$location.hashPath = '';
|
||||
$location.hashSearch = {};
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toEqual('http://server/');
|
||||
expect($location.hash).toEqual('');
|
||||
});
|
||||
|
||||
|
||||
it('should sync $location upon eval before watches are fired', function(){
|
||||
scope.$location = scope.$service('$location'); //publish to the scope for $watch
|
||||
|
||||
var log = '';
|
||||
scope.$watch('$location.hash', function(scope){
|
||||
log += scope.$location.hashPath + ';';
|
||||
});
|
||||
expect(log).toEqual('');
|
||||
scope.$digest();
|
||||
expect(log).toEqual(';');
|
||||
|
||||
log = '';
|
||||
scope.$location.hash = '/abc';
|
||||
scope.$digest();
|
||||
expect(scope.$location.hash).toEqual('/abc');
|
||||
expect(log).toEqual('/abc;');
|
||||
});
|
||||
|
||||
|
||||
describe('sync', function() {
|
||||
|
||||
it('should update hash with escaped hashPath', function() {
|
||||
$location.hashPath = 'foo=bar';
|
||||
scope.$digest();
|
||||
expect($location.hash).toBe('foo%3Dbar');
|
||||
beforeEach(function() {
|
||||
init('http://new.com/a/b#!', false, '/a/b', '!', true);
|
||||
});
|
||||
|
||||
|
||||
it('should give $location.href the highest precedence', function() {
|
||||
$location.hashPath = 'hashPath';
|
||||
$location.hashSearch = {hash:'search'};
|
||||
$location.hash = 'hash';
|
||||
$location.port = '333';
|
||||
$location.host = 'host';
|
||||
$location.href = 'https://hrefhost:23/hrefpath';
|
||||
it('should update $location when browser url changes', function() {
|
||||
spyOn($location, '$$parse').andCallThrough();
|
||||
$browser.url('http://new.com/a/b#!/aaa');
|
||||
$browser.poll();
|
||||
expect($location.absUrl()).toBe('http://new.com/a/b#!/aaa');
|
||||
expect($location.path()).toBe('/aaa');
|
||||
expect($location.$$parse).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
|
||||
it('should update browser when $location changes', function() {
|
||||
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough();
|
||||
$location.path('/new/path');
|
||||
expect($browserUrl).not.toHaveBeenCalled();
|
||||
scope.$apply();
|
||||
|
||||
expect($browserUrl).toHaveBeenCalledOnce();
|
||||
expect($browser.url()).toBe('http://new.com/a/b#!/new/path');
|
||||
});
|
||||
|
||||
|
||||
it('should update browser only once per $apply cycle', function() {
|
||||
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough();
|
||||
$location.path('/new/path');
|
||||
|
||||
scope.$watch(function() {
|
||||
$location.search('a=b');
|
||||
});
|
||||
|
||||
scope.$apply();
|
||||
expect($browserUrl).toHaveBeenCalledOnce();
|
||||
expect($browser.url()).toBe('http://new.com/a/b#!/new/path?a=b');
|
||||
});
|
||||
|
||||
|
||||
it('should replace browser url when url was replaced at least once', function() {
|
||||
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough();
|
||||
$location.path('/n/url').replace();
|
||||
scope.$apply();
|
||||
|
||||
expect($browserUrl).toHaveBeenCalledOnce();
|
||||
expect($browserUrl.mostRecentCall.args).toEqual(['http://new.com/a/b#!/n/url', true]);
|
||||
});
|
||||
|
||||
|
||||
it('should update the browser if changed from within a watcher', function() {
|
||||
scope.$watch(function() { return true; }, function() {
|
||||
$location.path('/changed');
|
||||
});
|
||||
|
||||
scope.$digest();
|
||||
|
||||
expect($location).toEqualData({href: 'https://hrefhost:23/hrefpath',
|
||||
protocol: 'https',
|
||||
host: 'hrefhost',
|
||||
port: '23',
|
||||
path: '/hrefpath',
|
||||
search: {},
|
||||
hash: '',
|
||||
hashPath: '',
|
||||
hashSearch: {}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should give $location.hash second highest precedence', function() {
|
||||
$location.hashPath = 'hashPath';
|
||||
$location.hashSearch = {hash:'search'};
|
||||
$location.hash = 'hash';
|
||||
$location.port = '333';
|
||||
$location.host = 'host';
|
||||
$location.path = '/path';
|
||||
|
||||
scope.$digest();
|
||||
|
||||
expect($location).toEqualData({href: 'http://host:333/path#hash',
|
||||
protocol: 'http',
|
||||
host: 'host',
|
||||
port: '333',
|
||||
path: '/path',
|
||||
search: {},
|
||||
hash: 'hash',
|
||||
hashPath: 'hash',
|
||||
hashSearch: {}
|
||||
});
|
||||
expect($browser.url()).toBe('http://new.com/a/b#!/changed');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('update()', function() {
|
||||
// html5 history is disabled
|
||||
describe('disabled history', function() {
|
||||
|
||||
it('should accept hash object and update only given properties', function() {
|
||||
$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
$location.update({host: 'new', port: 24});
|
||||
|
||||
expect($location.host).toEqual('new');
|
||||
expect($location.port).toEqual(24);
|
||||
expect($location.protocol).toEqual('http');
|
||||
expect($location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2=");
|
||||
it('should use hashbang url with hash prefix', function() {
|
||||
init('http://domain.com/base/index.html#!/a/b', false, '/base/index.html', '!');
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/a/b');
|
||||
$location.path('/new');
|
||||
$location.search({a: true});
|
||||
scope.$apply();
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/new?a');
|
||||
});
|
||||
|
||||
|
||||
it('should remove # if hash is empty', function() {
|
||||
$location.update('http://www.angularjs.org/index.php#');
|
||||
expect($location.href).toEqual('http://www.angularjs.org/index.php');
|
||||
});
|
||||
|
||||
|
||||
it('should clear hash when updating to hash-less URL', function() {
|
||||
$location.update('http://server');
|
||||
expect($location.href).toBe('http://server');
|
||||
expect($location.hash).toBe('');
|
||||
it('should use hashbang url without hash prefix', function() {
|
||||
init('http://domain.com/base/index.html#/a/b', false, '/base/index.html', '');
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#/a/b');
|
||||
$location.path('/new');
|
||||
$location.search({a: true});
|
||||
scope.$apply();
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#/new?a');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('updateHash()', function() {
|
||||
// html5 history enabled, but not supported by browser
|
||||
describe('history on old browser', function() {
|
||||
|
||||
it('should accept single string argument to update path', function() {
|
||||
$location.updateHash('path');
|
||||
expect($location.hash).toEqual('path');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
afterEach(dealocRootElement);
|
||||
|
||||
it('should use hashbang url with hash prefix', function() {
|
||||
init('http://domain.com/base/index.html#!!/a/b', true, '/base/index.html', '!!', false);
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!!/a/b');
|
||||
$location.path('/new');
|
||||
$location.search({a: true});
|
||||
scope.$apply();
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!!/new?a');
|
||||
});
|
||||
|
||||
|
||||
it('should reset hashSearch when updating with a single string', function() {
|
||||
$location.updateHash({foo:'bar'}); //set some initial state for hashSearch
|
||||
it('should redirect to hashbang url when new url given', function() {
|
||||
init('http://domain.com/base/new-path/index.html', true, '/base/index.html', '!');
|
||||
expect($browser.url()).toBe('http://domain.com/base/index.html#!/new-path/index.html');
|
||||
});
|
||||
});
|
||||
|
||||
$location.updateHash('path');
|
||||
expect($location.hashPath).toEqual('path');
|
||||
expect($location.hashSearch).toEqual({});
|
||||
|
||||
// html5 history enabled and supported by browser
|
||||
describe('history on new browser', function() {
|
||||
|
||||
afterEach(dealocRootElement);
|
||||
|
||||
it('should use new url', function() {
|
||||
init('http://domain.com/base/old/index.html#a', true, '/base/index.html', '', true);
|
||||
expect($browser.url()).toBe('http://domain.com/base/old/index.html#a');
|
||||
$location.path('/new');
|
||||
$location.search({a: true});
|
||||
scope.$apply();
|
||||
expect($browser.url()).toBe('http://domain.com/base/new?a#a');
|
||||
});
|
||||
|
||||
|
||||
it('should accept single object argument to update search', function() {
|
||||
$location.updateHash({a: 'b'});
|
||||
expect($location.hash).toEqual('?a=b');
|
||||
expect($location.hashSearch).toEqual({a: 'b'});
|
||||
it('should rewrite when hashbang url given', function() {
|
||||
init('http://domain.com/base/index.html#!/a/b', true, '/base/index.html', '!', true);
|
||||
expect($browser.url()).toBe('http://domain.com/base/a/b');
|
||||
$location.path('/new');
|
||||
$location.hash('abc');
|
||||
scope.$apply();
|
||||
expect($browser.url()).toBe('http://domain.com/base/new#abc');
|
||||
expect($location.path()).toBe('/new');
|
||||
});
|
||||
|
||||
|
||||
it('should accept path string and search object arguments to update both', function() {
|
||||
$location.updateHash('path', {a: 'b'});
|
||||
expect($location.hash).toEqual('path?a=b');
|
||||
expect($location.hashSearch).toEqual({a: 'b'});
|
||||
expect($location.hashPath).toEqual('path');
|
||||
});
|
||||
|
||||
|
||||
it('should update href and hash when updating to empty string', function() {
|
||||
$location.updateHash('');
|
||||
expect($location.href).toBe('http://server');
|
||||
expect($location.hash).toBe('');
|
||||
|
||||
scope.$digest();
|
||||
|
||||
expect($location.href).toBe('http://server');
|
||||
expect($location.hash).toBe('');
|
||||
it('should rewrite when hashbang url given (without hash prefix)', function() {
|
||||
init('http://domain.com/base/index.html#/a/b', true, '/base/index.html', '', true);
|
||||
expect($browser.url()).toBe('http://domain.com/base/a/b');
|
||||
expect($location.path()).toBe('/a/b');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -255,21 +473,21 @@ describe('$location', function() {
|
||||
it('should parse basic url', function() {
|
||||
var match = URL_MATCH.exec('http://www.angularjs.org/path?search#hash?x=x');
|
||||
|
||||
expect(match[1]).toEqual('http');
|
||||
expect(match[3]).toEqual('www.angularjs.org');
|
||||
expect(match[6]).toEqual('/path');
|
||||
expect(match[8]).toEqual('search');
|
||||
expect(match[10]).toEqual('hash?x=x');
|
||||
expect(match[1]).toBe('http');
|
||||
expect(match[3]).toBe('www.angularjs.org');
|
||||
expect(match[6]).toBe('/path');
|
||||
expect(match[8]).toBe('search');
|
||||
expect(match[10]).toBe('hash?x=x');
|
||||
});
|
||||
|
||||
|
||||
it('should parse file://', function(){
|
||||
var match = URL_MATCH.exec('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
|
||||
expect(match[1]).toEqual('file');
|
||||
expect(match[3]).toEqual('');
|
||||
expect(match[1]).toBe('file');
|
||||
expect(match[3]).toBe('');
|
||||
expect(match[5]).toBeFalsy();
|
||||
expect(match[6]).toEqual('/Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
expect(match[6]).toBe('/Users/Shared/misko/work/angular.js/scenario/widgets.html');
|
||||
expect(match[8]).toBeFalsy();
|
||||
});
|
||||
|
||||
@@ -277,30 +495,170 @@ describe('$location', function() {
|
||||
it('should parse url with "-" in host', function(){
|
||||
var match = URL_MATCH.exec('http://a-b1.c-d.09/path');
|
||||
|
||||
expect(match[1]).toEqual('http');
|
||||
expect(match[3]).toEqual('a-b1.c-d.09');
|
||||
expect(match[1]).toBe('http');
|
||||
expect(match[3]).toBe('a-b1.c-d.09');
|
||||
expect(match[5]).toBeFalsy();
|
||||
expect(match[6]).toEqual('/path');
|
||||
expect(match[6]).toBe('/path');
|
||||
expect(match[8]).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
it('should parse host without "/" at the end', function() {
|
||||
var match = URL_MATCH.exec('http://host.org');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
expect(match[3]).toBe('host.org');
|
||||
|
||||
match = URL_MATCH.exec('http://host.org#');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
expect(match[3]).toBe('host.org');
|
||||
|
||||
match = URL_MATCH.exec('http://host.org?');
|
||||
expect(match[3]).toEqual('host.org');
|
||||
expect(match[3]).toBe('host.org');
|
||||
});
|
||||
|
||||
|
||||
it('should match with just "/" path', function() {
|
||||
var match = URL_MATCH.exec('http://server/#?book=moby');
|
||||
|
||||
expect(match[10]).toEqual('?book=moby');
|
||||
expect(match[10]).toBe('?book=moby');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('PATH_MATCH', function() {
|
||||
|
||||
it('should parse just path', function() {
|
||||
var match = PATH_MATCH.exec('/path');
|
||||
expect(match[1]).toBe('/path');
|
||||
});
|
||||
|
||||
|
||||
it('should parse path with search', function() {
|
||||
var match = PATH_MATCH.exec('/ppp/a?a=b&c');
|
||||
expect(match[1]).toBe('/ppp/a');
|
||||
expect(match[3]).toBe('a=b&c');
|
||||
});
|
||||
|
||||
|
||||
it('should parse path with hash', function() {
|
||||
var match = PATH_MATCH.exec('/ppp/a#abc?');
|
||||
expect(match[1]).toBe('/ppp/a');
|
||||
expect(match[5]).toBe('abc?');
|
||||
});
|
||||
|
||||
|
||||
it('should parse path with both search and hash', function() {
|
||||
var match = PATH_MATCH.exec('/ppp/a?a=b&c#abc/d?');
|
||||
expect(match[3]).toBe('a=b&c');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('link rewriting', function() {
|
||||
|
||||
var root, link, extLink, $browser, originalBrowser, lastEventPreventDefault;
|
||||
|
||||
function init(linkHref, html5Mode, supportHist, attrs) {
|
||||
var jqRoot = jqLite('<div></div>');
|
||||
attrs = attrs ? ' ' + attrs + ' ' : '';
|
||||
link = jqLite('<a href="' + linkHref + '"' + attrs + '>link</a>')[0];
|
||||
root = jqRoot.append(link)[0];
|
||||
|
||||
jqLite(document.body).append(jqRoot);
|
||||
|
||||
var scope = angular.scope(null, {
|
||||
$document: jqRoot,
|
||||
$sniffer: {history: supportHist},
|
||||
$locationConfig: {html5Mode: html5Mode, hashPrefix: '!'}
|
||||
});
|
||||
|
||||
$browser = scope.$service('$browser');
|
||||
$browser.url('http://host.com/base');
|
||||
$browser.$$baseHref = '/base/index.html';
|
||||
var $location = scope.$service('$location');
|
||||
originalBrowser = $browser.url();
|
||||
|
||||
// we have to prevent the default operation, as we need to test absolute links (http://...)
|
||||
// and navigating to these links would kill jstd
|
||||
jqRoot.bind('click', function(e) {
|
||||
lastEventPreventDefault = e.isDefaultPrevented();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAndExpectRewriteTo(url) {
|
||||
browserTrigger(link, 'click');
|
||||
expect(lastEventPreventDefault).toBe(true);
|
||||
expect($browser.url()).toBe(url);
|
||||
}
|
||||
|
||||
function triggerAndExpectNoRewrite() {
|
||||
browserTrigger(link, 'click');
|
||||
expect(lastEventPreventDefault).toBe(false);
|
||||
expect($browser.url()).toBe(originalBrowser);
|
||||
}
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(root);
|
||||
dealoc(document.body);
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite rel link to new url when history enabled on new browser', function() {
|
||||
init('link?a#b', true, true);
|
||||
triggerAndExpectRewriteTo('http://host.com/base/link?a#b');
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite abs link to new url when history enabled on new browser', function() {
|
||||
init('/base/link?a#b', true, true);
|
||||
triggerAndExpectRewriteTo('http://host.com/base/link?a#b');
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite rel link to hashbang url when history enabled on old browser', function() {
|
||||
init('link?a#b', true, false);
|
||||
triggerAndExpectRewriteTo('http://host.com/base/index.html#!/link?a#b');
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite abs link to hashbang url when history enabled on old browser', function() {
|
||||
init('/base/link?a#b', true, false);
|
||||
triggerAndExpectRewriteTo('http://host.com/base/index.html#!/link?a#b');
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite when history disabled', function() {
|
||||
init('#new', false);
|
||||
triggerAndExpectNoRewrite();
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite ng:ext-link', function() {
|
||||
init('#new', true, true, 'ng:ext-link');
|
||||
triggerAndExpectNoRewrite();
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite full url links do different domain', function() {
|
||||
init('http://www.dot.abc/a?b=c', true);
|
||||
triggerAndExpectNoRewrite();
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite links with target="_blank"', function() {
|
||||
init('/a?b=c', true, true, 'target="_blank"');
|
||||
triggerAndExpectNoRewrite();
|
||||
});
|
||||
|
||||
|
||||
it('should not rewrite links with target specified', function() {
|
||||
init('/a?b=c', true, true, 'target="some-frame"');
|
||||
triggerAndExpectNoRewrite();
|
||||
});
|
||||
|
||||
|
||||
it('should rewrite full url links to same domain and base path', function() {
|
||||
init('http://host.com/base/new', true);
|
||||
triggerAndExpectRewriteTo('http://host.com/base/index.html#!/new');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,11 +10,11 @@ describe('$routeParams', function(){
|
||||
$route.when('/foo');
|
||||
$route.when('/bar/:barId');
|
||||
|
||||
$location.hash = '/foo?a=b';
|
||||
$location.path('/foo').search('a=b');
|
||||
scope.$digest();
|
||||
expect($routeParams).toEqual({a:'b'});
|
||||
|
||||
$location.hash = '/bar/123?x=abc';
|
||||
$location.path('/bar/123').search('x=abc');
|
||||
scope.$digest();
|
||||
expect($routeParams).toEqual({barId:'123', x:'abc'});
|
||||
});
|
||||
@@ -30,11 +30,11 @@ describe('$routeParams', function(){
|
||||
$route.when('/foo');
|
||||
$route.when('/bar/:barId');
|
||||
|
||||
$location.hash = '/foo?a=b';
|
||||
$location.path('/foo').search('a=b');
|
||||
scope.$digest();
|
||||
expect(scope.$service('$routeParams')).toBe(firstRouteParams);
|
||||
|
||||
$location.hash = '/bar/123?x=abc';
|
||||
$location.path('/bar/123').search('x=abc');
|
||||
scope.$digest();
|
||||
expect(scope.$service('$routeParams')).toBe(firstRouteParams);
|
||||
});
|
||||
|
||||
+135
-154
@@ -1,60 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
describe('$route', function() {
|
||||
var scope;
|
||||
var scope, $route, $location;
|
||||
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$location = scope.$service('$location');
|
||||
$route = scope.$service('$route');
|
||||
});
|
||||
|
||||
|
||||
afterEach(function(){
|
||||
dealoc(scope);
|
||||
});
|
||||
|
||||
|
||||
it('should route and fire change event', function(){
|
||||
it('should route and fire change event', function() {
|
||||
var log = '',
|
||||
$location, $route,
|
||||
lastRoute,
|
||||
nextRoute;
|
||||
|
||||
function BookChapter() {
|
||||
log += '<init>;';
|
||||
}
|
||||
scope = compile('<div></div>')();
|
||||
$location = scope.$service('$location');
|
||||
$route = scope.$service('$route');
|
||||
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'});
|
||||
|
||||
$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template: 'Chapter.html'});
|
||||
$route.when('/Blank');
|
||||
scope.$on('$beforeRouteChange', function(event, next, current){
|
||||
scope.$on('$beforeRouteChange', function(event, next, current) {
|
||||
log += 'before();';
|
||||
expect(current).toBe($route.current);
|
||||
lastRoute = current;
|
||||
nextRoute = next;
|
||||
});
|
||||
scope.$on('$afterRouteChange', function(event, current, last){
|
||||
scope.$on('$afterRouteChange', function(event, current, last) {
|
||||
log += 'after();';
|
||||
expect(current).toBe($route.current);
|
||||
expect(lastRoute).toBe(last);
|
||||
expect(nextRoute).toBe(current);
|
||||
});
|
||||
|
||||
$location.update('http://server#/Book/Moby/Chapter/Intro?p=123');
|
||||
$location.path('/Book/Moby/Chapter/Intro').search('p=123');
|
||||
scope.$digest();
|
||||
expect(log).toEqual('before();<init>;after();');
|
||||
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
|
||||
var lastId = $route.current.scope.$id;
|
||||
|
||||
log = '';
|
||||
$location.update('http://server#/Blank?ignore');
|
||||
$location.path('/Blank').search('ignore');
|
||||
scope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current.params).toEqual({ignore:true});
|
||||
expect($route.current.scope.$id).not.toEqual(lastId);
|
||||
|
||||
log = '';
|
||||
$location.update('http://server#/NONE');
|
||||
$location.path('/NONE');
|
||||
scope.$digest();
|
||||
expect(log).toEqual('before();after();');
|
||||
expect($route.current).toEqual(null);
|
||||
@@ -65,18 +59,52 @@ describe('$route', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should match a route that contains special chars in the path', function() {
|
||||
$route.when('/$test.23/foo(bar)/:baz', {template: 'test.html'});
|
||||
|
||||
$location.path('/test');
|
||||
scope.$digest();
|
||||
expect($route.current).toBeUndefined();
|
||||
|
||||
$location.path('/$testX23/foo(bar)/222');
|
||||
scope.$digest();
|
||||
expect($route.current).toBeUndefined();
|
||||
|
||||
$location.path('/$test.23/foo(bar)/222');
|
||||
scope.$digest();
|
||||
expect($route.current).toBeDefined();
|
||||
|
||||
$location.path('/$test.23/foo\\(bar)/222');
|
||||
scope.$digest();
|
||||
expect($route.current).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
it('should change route even when only search param changes', function() {
|
||||
var callback = jasmine.createSpy('onRouteChange');
|
||||
|
||||
$route.when('/test', {template: 'test.html'});
|
||||
scope.$on('$beforeRouteChange', callback);
|
||||
$location.path('/test');
|
||||
scope.$digest();
|
||||
callback.reset();
|
||||
|
||||
$location.search({any: true});
|
||||
scope.$digest();
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should allow routes to be defined with just templates without controllers', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
onChangeSpy = jasmine.createSpy('onChange');
|
||||
var onChangeSpy = jasmine.createSpy('onChange');
|
||||
|
||||
$route.when('/foo', {template: 'foo.html'});
|
||||
scope.$on('$beforeRouteChange', onChangeSpy);
|
||||
expect($route.current).toBeUndefined();
|
||||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toEqual('foo.html');
|
||||
@@ -86,10 +114,7 @@ describe('$route', function() {
|
||||
|
||||
|
||||
it('should handle unknown routes with "otherwise" route definition', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
onChangeSpy = jasmine.createSpy('onChange');
|
||||
var onChangeSpy = jasmine.createSpy('onChange');
|
||||
|
||||
function NotFoundCtrl() {this.notFoundProp = 'not found!';}
|
||||
|
||||
@@ -99,7 +124,7 @@ describe('$route', function() {
|
||||
expect($route.current).toBeUndefined();
|
||||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/unknownRoute');
|
||||
$location.path('/unknownRoute');
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toBe('404.html');
|
||||
@@ -108,7 +133,7 @@ describe('$route', function() {
|
||||
expect(onChangeSpy).toHaveBeenCalled();
|
||||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
|
||||
expect($route.current.template).toEqual('foo.html');
|
||||
@@ -117,34 +142,30 @@ describe('$route', function() {
|
||||
expect(onChangeSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should $destroy old routes', function(){
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route');
|
||||
|
||||
$route.when('/foo', {template: 'foo.html', controller: function(){ this.name = 'FOO';}});
|
||||
$route.when('/bar', {template: 'bar.html', controller: function(){ this.name = 'BAR';}});
|
||||
it('should $destroy old routes', function() {
|
||||
$route.when('/foo', {template: 'foo.html', controller: function() {this.name = 'FOO';}});
|
||||
$route.when('/bar', {template: 'bar.html', controller: function() {this.name = 'BAR';}});
|
||||
$route.when('/baz', {template: 'baz.html'});
|
||||
|
||||
expect(scope.$childHead).toEqual(null);
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
|
||||
$location.updateHash('/bar');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
return
|
||||
|
||||
$location.updateHash('/baz');
|
||||
$location.path('/bar');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
|
||||
$location.updateHash('/');
|
||||
$location.path('/baz');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toBeTruthy();
|
||||
expect(scope.$$childHead).toEqual(scope.$$childTail);
|
||||
|
||||
$location.path('/');
|
||||
scope.$digest();
|
||||
expect(scope.$$childHead).toEqual(null);
|
||||
expect(scope.$$childTail).toEqual(null);
|
||||
@@ -152,15 +173,10 @@ describe('$route', function() {
|
||||
|
||||
|
||||
describe('redirection', function() {
|
||||
|
||||
it('should support redirection via redirectTo property by updating $location', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$browser = scope.$service('$browser'),
|
||||
$route = scope.$service('$route'),
|
||||
onChangeSpy = jasmine.createSpy('onChange');
|
||||
var onChangeSpy = jasmine.createSpy('onChange');
|
||||
|
||||
$route.when('', {redirectTo: '/foo'});
|
||||
$route.when('/', {redirectTo: '/foo'});
|
||||
$route.when('/foo', {template: 'foo.html'});
|
||||
$route.when('/bar', {template: 'bar.html'});
|
||||
$route.when('/baz', {redirectTo: '/bar'});
|
||||
@@ -169,108 +185,86 @@ describe('$route', function() {
|
||||
expect($route.current).toBeUndefined();
|
||||
expect(onChangeSpy).not.toHaveBeenCalled();
|
||||
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/foo');
|
||||
expect($route.current.template).toBe('foo.html');
|
||||
expect(onChangeSpy.callCount).toBe(2);
|
||||
|
||||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('');
|
||||
scope.$digest(); //match the redirect route + update $browser
|
||||
$browser.defer.flush(); //match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/foo');
|
||||
$location.path('/');
|
||||
scope.$digest();
|
||||
expect($location.path()).toBe('/foo');
|
||||
expect($route.current.template).toBe('foo.html');
|
||||
expect(onChangeSpy.callCount).toBe(2);
|
||||
|
||||
onChangeSpy.reset();
|
||||
$location.updateHash('/baz');
|
||||
scope.$digest(); //match the redirect route + update $browser
|
||||
$browser.defer.flush(); //match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar');
|
||||
$location.path('/baz');
|
||||
scope.$digest();
|
||||
expect($location.path()).toBe('/bar');
|
||||
expect($route.current.template).toBe('bar.html');
|
||||
expect(onChangeSpy.callCount).toBe(2);
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate route variables in the redirected hashPath from the original hashPath',
|
||||
function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$browser = scope.$service('$browser'),
|
||||
$route = scope.$service('$route');
|
||||
|
||||
it('should interpolate route vars in the redirected path from original path', function() {
|
||||
$route.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'});
|
||||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
|
||||
$location.path('/foo/id1/foo/subid3/gah');
|
||||
scope.$digest();
|
||||
|
||||
$location.updateHash('/foo/id1/foo/subid3/gah');
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar/id1/subid3/23?extraId=gah');
|
||||
expect($route.current.template).toBe('bar.html');
|
||||
expect($location.path()).toEqual('/bar/id1/subid3/23');
|
||||
expect($location.search()).toEqual({extraId: 'gah'});
|
||||
expect($route.current.template).toEqual('bar.html');
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate route variables in the redirected hashPath from the original hashSearch',
|
||||
function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$browser = scope.$service('$browser'),
|
||||
$route = scope.$service('$route');
|
||||
|
||||
it('should interpolate route vars in the redirected path from original search', function() {
|
||||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'});
|
||||
|
||||
$location.path('/foo/id3/eId').search('subid=sid1&appended=true');
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '/foo/id3/eId?subid=sid1&appended=true';
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
|
||||
expect($location.hash).toBe('/bar/id3/sid1/99?appended=true&extra=eId');
|
||||
expect($route.current.template).toBe('bar.html');
|
||||
expect($location.path()).toEqual('/bar/id3/sid1/99');
|
||||
expect($location.search()).toEqual({appended: 'true', extra: 'eId'});
|
||||
expect($route.current.template).toEqual('bar.html');
|
||||
});
|
||||
|
||||
|
||||
it('should allow custom redirectTo function to be used', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$browser = scope.$service('$browser'),
|
||||
$route = scope.$service('$route');
|
||||
|
||||
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
|
||||
$route.when('/foo/:id',
|
||||
{redirectTo: customRedirectFn});
|
||||
$route.when('/foo/:id', {redirectTo: customRedirectFn});
|
||||
|
||||
$location.path('/foo/id3').search('subid=sid1&appended=true');
|
||||
scope.$digest();
|
||||
|
||||
$location.hash = '/foo/id3?subid=sid1&appended=true';
|
||||
scope.$digest(); //triggers initial route change - match the redirect route
|
||||
$browser.defer.flush(); //triger route change - match the route we redirected to
|
||||
expect($location.path()).toEqual('/custom');
|
||||
|
||||
expect($location.hash).toBe('custom');
|
||||
|
||||
function customRedirectFn(routePathParams, hash, hashPath, hashSearch) {
|
||||
function customRedirectFn(routePathParams, path, search) {
|
||||
expect(routePathParams).toEqual({id: 'id3'});
|
||||
expect(hash).toEqual($location.hash);
|
||||
expect(hashPath).toEqual($location.hashPath);
|
||||
expect(hashSearch).toEqual($location.hashSearch);
|
||||
return 'custom';
|
||||
expect(path).toEqual($location.path());
|
||||
expect(search).toEqual($location.search());
|
||||
return '/custom';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should replace the url when redirecting', function() {
|
||||
$route.when('/bar/:id', {template: 'bar.html'});
|
||||
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id'});
|
||||
|
||||
var replace;
|
||||
scope.$watch(function() {
|
||||
if (isUndefined(replace)) replace = $location.$$replace;
|
||||
});
|
||||
|
||||
$location.path('/foo/id3/eId');
|
||||
scope.$digest();
|
||||
|
||||
expect($location.path()).toEqual('/bar/id3');
|
||||
expect(replace).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('reloadOnSearch', function() {
|
||||
it('should reload a route when reloadOnSearch is enabled and hashSearch changes', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
$rouetParams = scope.$service('$routeParams'),
|
||||
it('should reload a route when reloadOnSearch is enabled and .search() changes', function() {
|
||||
var $routeParams = scope.$service('$routeParams'),
|
||||
reloaded = jasmine.createSpy('route reload');
|
||||
|
||||
$route.when('/foo', {controller: FooCtrl});
|
||||
@@ -280,26 +274,23 @@ describe('$route', function() {
|
||||
reloaded();
|
||||
}
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect($rouetParams).toEqual({});
|
||||
expect($routeParams).toEqual({});
|
||||
reloaded.reset();
|
||||
|
||||
// trigger reload
|
||||
$location.hashSearch.foo = 'bar';
|
||||
$location.search({foo: 'bar'});
|
||||
scope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect($rouetParams).toEqual({foo:'bar'});
|
||||
expect($routeParams).toEqual({foo:'bar'});
|
||||
});
|
||||
|
||||
|
||||
it('should not reload a route when reloadOnSearch is disabled and only hashSearch changes',
|
||||
it('should not reload a route when reloadOnSearch is disabled and only .search() changes',
|
||||
function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
reloaded = jasmine.createSpy('route reload'),
|
||||
var reloaded = jasmine.createSpy('route reload'),
|
||||
routeUpdateEvent = jasmine.createSpy('route reload');
|
||||
|
||||
$route.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
|
||||
@@ -312,14 +303,14 @@ describe('$route', function() {
|
||||
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect(routeUpdateEvent).not.toHaveBeenCalled();
|
||||
reloaded.reset();
|
||||
|
||||
// don't trigger reload
|
||||
$location.hashSearch.foo = 'bar';
|
||||
$location.search({foo: 'bar'});
|
||||
scope.$digest();
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
expect(routeUpdateEvent).toHaveBeenCalled();
|
||||
@@ -327,10 +318,7 @@ describe('$route', function() {
|
||||
|
||||
|
||||
it('should reload reloadOnSearch route when url differs only in route path param', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
reloaded = jasmine.createSpy('routeReload'),
|
||||
var reloaded = jasmine.createSpy('routeReload'),
|
||||
onRouteChange = jasmine.createSpy('onRouteChange');
|
||||
|
||||
$route.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
|
||||
@@ -343,32 +331,29 @@ describe('$route', function() {
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
expect(onRouteChange).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/foo/aaa');
|
||||
$location.path('/foo/aaa');
|
||||
scope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect(onRouteChange).toHaveBeenCalled();
|
||||
reloaded.reset();
|
||||
onRouteChange.reset();
|
||||
|
||||
$location.updateHash('/foo/bbb');
|
||||
$location.path('/foo/bbb');
|
||||
scope.$digest();
|
||||
expect(reloaded).toHaveBeenCalled();
|
||||
expect(onRouteChange).toHaveBeenCalled();
|
||||
reloaded.reset();
|
||||
onRouteChange.reset();
|
||||
|
||||
$location.hashSearch.foo = 'bar';
|
||||
$location.search({foo: 'bar'});
|
||||
scope.$digest();
|
||||
expect(reloaded).not.toHaveBeenCalled();
|
||||
expect(onRouteChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should update route params when reloadOnSearch is disabled and hashSearch', function() {
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
routeParams = jasmine.createSpy('routeParams');
|
||||
it('should update params when reloadOnSearch is disabled and .search() changes', function() {
|
||||
var routeParams = jasmine.createSpy('routeParams');
|
||||
|
||||
$route.when('/foo', {controller: FooCtrl});
|
||||
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
|
||||
@@ -383,48 +368,45 @@ describe('$route', function() {
|
||||
|
||||
expect(routeParams).not.toHaveBeenCalled();
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
scope.$digest();
|
||||
expect(routeParams).toHaveBeenCalledWith({});
|
||||
routeParams.reset();
|
||||
|
||||
// trigger reload
|
||||
$location.hashSearch.foo = 'bar';
|
||||
$location.search({foo: 'bar'});
|
||||
scope.$digest();
|
||||
expect(routeParams).toHaveBeenCalledWith({foo: 'bar'});
|
||||
routeParams.reset();
|
||||
|
||||
$location.updateHash('/bar/123');
|
||||
$location.path('/bar/123').search({});
|
||||
scope.$digest();
|
||||
expect(routeParams).toHaveBeenCalledWith({barId: '123'});
|
||||
routeParams.reset();
|
||||
|
||||
// don't trigger reload
|
||||
$location.hashSearch.foo = 'bar';
|
||||
$location.search({foo: 'bar'});
|
||||
scope.$digest();
|
||||
expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'});
|
||||
});
|
||||
|
||||
|
||||
describe('reload', function(){
|
||||
describe('reload', function() {
|
||||
|
||||
it('should reload even if reloadOnSearch is false', function(){
|
||||
var scope = angular.scope(),
|
||||
$location = scope.$service('$location'),
|
||||
$route = scope.$service('$route'),
|
||||
$routeParams = scope.$service('$routeParams'),
|
||||
it('should reload even if reloadOnSearch is false', function() {
|
||||
var $routeParams = scope.$service('$routeParams'),
|
||||
count = 0;
|
||||
|
||||
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
|
||||
|
||||
function FooCtrl() { count ++; }
|
||||
|
||||
$location.updateHash('/bar/123');
|
||||
$location.path('/bar/123');
|
||||
scope.$digest();
|
||||
expect($routeParams).toEqual({barId:'123'});
|
||||
expect(count).toEqual(1);
|
||||
|
||||
$location.hash = '/bar/123?a=b';
|
||||
$location.path('/bar/123').search('a=b');
|
||||
scope.$digest();
|
||||
expect($routeParams).toEqual({barId:'123', a:'b'});
|
||||
expect(count).toEqual(1);
|
||||
@@ -435,6 +417,5 @@ describe('$route', function() {
|
||||
expect(count).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
describe('$sniffer', function() {
|
||||
|
||||
function sniffer($window) {
|
||||
return angular.service('$sniffer')($window);
|
||||
}
|
||||
|
||||
describe('history', function() {
|
||||
it('should be true if history.pushState defined', function() {
|
||||
expect(sniffer({history: {pushState: noop, replaceState: noop}}).history).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false if history or pushState not defined', function() {
|
||||
expect(sniffer({history: {}}).history).toBe(false);
|
||||
expect(sniffer({}).history).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hashchange', function() {
|
||||
it('should be true if onhashchange property defined', function() {
|
||||
expect(sniffer({onhashchange: true, document: {}}).hashchange).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false if onhashchange property not defined', function() {
|
||||
expect(sniffer({document: {}}).hashchange).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false if documentMode is 7 (IE8 comp mode)', function() {
|
||||
expect(sniffer({onhashchange: true, document: {documentMode: 7}}).hashchange).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use Modernizr if defined', function() {
|
||||
var Modernizr = {};
|
||||
expect(sniffer({Modernizr: Modernizr})).toBe(Modernizr);
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,14 @@ if (window.jstestdriver) {
|
||||
beforeEach(function(){
|
||||
// This is to reset parsers global cache of expressions.
|
||||
compileCache = {};
|
||||
|
||||
// workaround for IE bug https://plus.google.com/104744871076396904202/posts/Kqjuj6RSbbT
|
||||
// IE overwrite window.jQuery with undefined because of empty jQuery var statement, so we have to
|
||||
// correct this, but only if we are not running in jqLite mode
|
||||
if (!_jqLiteMode && _jQuery !== jQuery) {
|
||||
jQuery = _jQuery;
|
||||
}
|
||||
|
||||
// reset to jQuery or default to us.
|
||||
bindJQuery();
|
||||
jqLite(document.body).html('');
|
||||
@@ -112,6 +120,11 @@ beforeEach(function(){
|
||||
};
|
||||
|
||||
return this.actual.callCount == 1;
|
||||
},
|
||||
|
||||
|
||||
toBeOneOf: function() {
|
||||
return angularArray.indexOf(arguments, this.actual) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+23
-17
@@ -900,22 +900,28 @@ describe("widget", function(){
|
||||
createSelect({
|
||||
name:'selected',
|
||||
'ng:options':'value for value in values',
|
||||
'ng:change':'count = count + 1'
|
||||
'ng:change':'log = log + selected.name'
|
||||
});
|
||||
scope.values = [{name:'A'}, {name:'B'}];
|
||||
scope.selected = scope.values[0];
|
||||
scope.count = 0;
|
||||
scope.log = '';
|
||||
scope.$digest();
|
||||
expect(scope.count).toEqual(0);
|
||||
expect(scope.log).toEqual('');
|
||||
|
||||
select.val('1');
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.count).toEqual(1);
|
||||
expect(scope.log).toEqual('B');
|
||||
expect(scope.selected).toEqual(scope.values[1]);
|
||||
|
||||
// ignore change event when the model doesn't change
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.count).toEqual(1);
|
||||
expect(scope.log).toEqual('B');
|
||||
expect(scope.selected).toEqual(scope.values[1]);
|
||||
|
||||
select.val('0');
|
||||
browserTrigger(select, 'change');
|
||||
expect(scope.log).toEqual('BA');
|
||||
expect(scope.selected).toEqual(scope.values[0]);
|
||||
});
|
||||
|
||||
it('should update model on change through expression', function(){
|
||||
@@ -951,8 +957,8 @@ describe("widget", function(){
|
||||
scope.selected = [];
|
||||
scope.$digest();
|
||||
expect(select.find('option').length).toEqual(2);
|
||||
expect(jqLite(select.find('option')[0]).attr('selected')).toEqual(false);
|
||||
expect(jqLite(select.find('option')[1]).attr('selected')).toEqual(false);
|
||||
expect(select.find('option')[0].selected).toBe(false);
|
||||
expect(select.find('option')[1].selected).toBe(false);
|
||||
|
||||
scope.selected.push(scope.values[1]);
|
||||
scope.$digest();
|
||||
@@ -1111,7 +1117,7 @@ describe("widget", function(){
|
||||
|
||||
|
||||
it('should do nothing when no routes are defined', function() {
|
||||
$location.updateHash('/unknown');
|
||||
$location.path('/unknown');
|
||||
rootScope.$digest();
|
||||
expect(rootScope.$element.text()).toEqual('');
|
||||
});
|
||||
@@ -1123,14 +1129,14 @@ describe("widget", function(){
|
||||
|
||||
expect(rootScope.$element.text()).toEqual('');
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>');
|
||||
rootScope.$digest();
|
||||
rootScope.$digest();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('4');
|
||||
|
||||
$location.updateHash('/bar');
|
||||
$location.path('/bar');
|
||||
$browser.xhr.expectGET('myUrl2').respond('angular is da best');
|
||||
rootScope.$digest();
|
||||
rootScope.$digest();
|
||||
@@ -1141,14 +1147,14 @@ describe("widget", function(){
|
||||
it('should remove all content when location changes to an unknown route', function() {
|
||||
$route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{1+3}}</div>');
|
||||
rootScope.$digest();
|
||||
rootScope.$digest();
|
||||
$browser.xhr.flush();
|
||||
expect(rootScope.$element.text()).toEqual('4');
|
||||
|
||||
$location.updateHash('/unknown');
|
||||
$location.path('/unknown');
|
||||
rootScope.$digest();
|
||||
rootScope.$digest();
|
||||
expect(rootScope.$element.text()).toEqual('');
|
||||
@@ -1158,7 +1164,7 @@ describe("widget", function(){
|
||||
$route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
|
||||
rootScope.parentVar = 'parent';
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
$browser.xhr.expectGET('myUrl1').respond('<div>{{parentVar}}</div>');
|
||||
rootScope.$digest();
|
||||
rootScope.$digest();
|
||||
@@ -1177,7 +1183,7 @@ describe("widget", function(){
|
||||
var myApp = angular.scope();
|
||||
var $browser = myApp.$service('$browser');
|
||||
$browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>');
|
||||
$browser.setUrl('http://server/#/foo');
|
||||
myApp.$service('$location').path('/foo');
|
||||
|
||||
var $route = myApp.$service('$route');
|
||||
$route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'});
|
||||
@@ -1214,7 +1220,7 @@ describe("widget", function(){
|
||||
this.log.push('child');
|
||||
};
|
||||
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
$browser.xhr.expectGET('viewPartial.html').
|
||||
respond('<div ng:init="log.push(\'init\')">' +
|
||||
'<div ng:controller="ChildCtrl"></div>' +
|
||||
@@ -1224,12 +1230,12 @@ describe("widget", function(){
|
||||
|
||||
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
|
||||
$location.updateHash('');
|
||||
$location.path('/');
|
||||
rootScope.$apply();
|
||||
expect(rootScope.log).toEqual(['parent', 'init', 'child']);
|
||||
|
||||
rootScope.log = [];
|
||||
$location.updateHash('/foo');
|
||||
$location.path('/foo');
|
||||
rootScope.$apply();
|
||||
$browser.defer.flush();
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# AngularJS build config file
|
||||
---
|
||||
version: 0.10.0
|
||||
codename: chicken-hands
|
||||
version: 0.10.2
|
||||
codename: sneaky-seagull
|
||||
|
||||
Reference in New Issue
Block a user