Compare commits

...

63 Commits

Author SHA1 Message Date
Igor Minar f6a8ad87ee chore(release): cutting the 0.10.2 sneaky-seagull release 2011-10-08 09:18:19 -07:00
Igor Minar 7e6ff401b8 docs(changelog): release notes for 0.10.2 sneaky-seagull 2011-10-08 09:15:10 -07:00
Dhruv Manek 7aeb6a24f7 fix(docs): rename doc dev_guide.templates.css to dev_guide.templates.css-styling
Closes #580
2011-10-07 18:20:20 -07:00
Dhruv Manek e1ecc34edd fix(parser): Fix short circuit of logical AND and OR operators
Closes #433
2011-10-07 16:01:09 -07:00
Misko Hevery 29d36e94e1 feat(gdocs): better error handling 2011-10-05 14:19:33 -07:00
dandoyon 091c173632 docs(started): fixing pluralization 2011-10-05 13:47:43 -07:00
TEHEK Firefox c115fa9924 fix($limitTo): properly handle excessive limits
`angular.Array.limitTo`'s  result should not exceed original input array size

Closes #571
2011-10-05 11:01:32 -07:00
Igor Minar b7a7fc7065 doc($log): fix the $log service example
is no longer auto-published on the root scope, so we need to publish
it via a controller
2011-09-29 03:21:24 +02:00
Igor Minar 21b2a5bd21 chore(directives): add a todo for ng:style 2011-09-28 23:57:01 +02:00
Igor Minar ca1e45beaf fix(jqLite): css should convert dash-separated properties to camelCase
this fix is needed for Firefox or other browsers that strictly follow
dom/css spec which states that element.style should make properties
available in camelCased form.

Closes #569
2011-09-28 23:57:00 +02:00
Igor Minar 084b83ffa9 test(matchers): add toBeOneOf matcher 2011-09-27 21:48:47 +02:00
Marcello Nuccio bf5e5f7bc9 fix($resource): action defaults should override resource defaults
defaults definned per action should take precedence over defaults
defined for the whole resource.

This is potentialy a BREAKING CHANGE in case someone relied on the buggy
behavior.
2011-09-27 00:44:10 +02:00
Igor Minar 2e9fed7b6c fix(e2e): add index-nocache.html to run e2e tests without cache
using appcache while running e2e tests was causing the following
problems:
- Safari would occasionally reload the app (as a result of the appcache
  refresh) during the angular.validator.asychronous test, which would
  result in test failure and false positivy.
- Firefox6 would run the tests very slowly, disabling the cache resolved
  the latency issues
- Sometimes tests would run with stale code pulled from cache, which
  would result in flaky tests.
2011-09-26 23:51:55 +02:00
Igor Minar ea3228e311 fix(scenario): workaround for FF6 dispatchEvent issue #684208 2011-09-26 23:51:54 +02:00
Igor Minar 2eb49147d6 Revert "fix(scenario): temporary fix for FF6"
This reverts commit aac68bf2ba.
2011-09-26 23:51:54 +02:00
Vojta Jina 13f92de624 feat(docs): use html5 history api for all routing in the docs app
- Configure our docs app to use new $location with html5 history api!

- Update simple node web server to serve index.html for all links
  (rewritting).

- Update .htaccess file to serve index.html for all links (rewritting).

- At runtime determine the base href path and attach it to the DOM. We
  needed the absolute URL to get all browsers to work well.

- Because of the above, we also need to dynamically determine all needed
  js/css resources and add them to the DOM. This was needed because FF6
  would eagerly fetch resources with wrong URL since the base element is
  added to the dom at runtime.

- All content html files were moved to the partials directory, because
  with the new html5 urls it was impossible to tell if request for
  http://domain/api/angular.filter.html was an html5 url for the html
  filter doc page, or an xhr/appcache request for the content html file
  for the html filter.

f
2011-09-26 23:51:53 +02:00
Igor Minar 2bc39bb0b4 fix($route): fix regex escaping in route matcher 2011-09-21 13:47:17 +02:00
Igor Minar 62ae7fccbc fix(angular-mocks): fix .defer.cancel when i=0 2011-09-16 14:18:45 +02:00
Igor Minar 3ace81b92a fix(e2e tests): use prop() instead of attr() and quote attributes
Because of changes in jQuery, we need to use element().prop() instead of element().attr() to retrieve className and other element properties.

Additionally all attribute selectors (e.g. input[name=value]) must have value quoted if it contains dots (".").
2011-09-16 02:44:35 +02:00
Igor Minar 9acf45127e fix(scenarioRunner): navigateTo should use prop('contentWindow')
... instead of attr('contentWindow')
2011-09-16 02:44:35 +02:00
Igor Minar 6883e8c7a0 feat(scenarioRunner): adding support for element().prop()
since jQuery 1.6.4 attr() focuses only on work with element attributes and doesn't deal well with element properties, so adding prop() support is required for getting many e2e tests to pass after upgrading the runner to jQuery 1.6.4.
2011-09-16 02:44:34 +02:00
Igor Minar 7ae536d053 fix(specs): various fixes to get IE8+jquery unit tests green 2011-09-16 02:44:33 +02:00
Igor Minar 2170c06924 fix(specs): fix jQuery to jqLite binding on IE8
It appears that this whole time all IE8 unit tests ran only with jqLite. Due to a weird bug in IE[1], we never overwrote jqLite implementation with jQuery, so the tests ran with our jqLite instead.

This affected only IE8 (and likely older) and only in unit testing mode. angular.js - the built artifact binds to jQuery just find.

[1] https://plus.google.com/104744871076396904202/posts/Kqjuj6RSbbT
2011-09-16 02:44:33 +02:00
Igor Minar 0e5a24c584 fix(specs): jQuery now returns attr name instead of true/false for special attrs
for special attrs like 'checked' or 'multiple', jquery now returns the name or undedefined.

e.g. foo.attr('checked') => 'checked' || undefined

The solution is a combination of updating our tests as well as switching
over to prop() instead which properly returns true/false
2011-09-16 02:44:32 +02:00
Igor Minar 4e8f0d6e9f fix($location) $location specs must unbind document listener
link rewriting used in html5 mode on legacy browsers binds to document.onClick - we need to destroy this listener after each test to prevent test collisions (global state is evil).
2011-09-16 02:44:31 +02:00
Igor Minar f9b6b61468 fix(scenario dsl): jQuery getters must have no value
jQuery now requires getter methods to have no value at all -
they do arguments.length check to identify getters vs setters.
2011-09-16 02:44:31 +02:00
Igor Minar 555f415290 fix(ng:options): fix selecting options
Contains 3 fixes:

- the internal model was by mistake using "checked" property instead of
  "selected"
- use jqLite.prop() to set 'selected' property
- added inChangeEvent check - we should not interfere with the browser
  selecting elements when not necessary
2011-09-16 02:44:30 +02:00
Igor Minar 3800d17703 feat(jqLite): add prop() support
since jQuery 1.6.4 prop() became very important because attr() does't have access to certain properties any more (e.g. className), so I'm adding it to jqLite as well so that jqLite preserves the feature-set it had before the jQuery upgrade.
2011-09-16 02:44:30 +02:00
Igor Minar 009059dd1b fix(jqLite): make css() on IE8 behave the same way as jQuery 1.6.4 2011-09-16 02:44:29 +02:00
Igor Minar 6b7ddf414d feat(jqLite): add support for unbind()
supports these invocation types:

- foo.unbind();
- foo.unbind('eventType');
- foo.unbind('eventType', fn);

more info: http://api.jquery.com/unbind/
2011-09-16 02:44:29 +02:00
Igor Minar 8259f10138 fix(jqLite): make attr() compatible with jQuery 1.6.4
The behavior of attr() getter and setter changed in jQuery 1.6 and now they treat element properties and attributes as two different things, but in order to not break everyone there is a partial backwards compatibility for checking and updating element properties as well. see http://api.jquery.com/prop/ for more info.
2011-09-16 02:44:28 +02:00
Igor Minar ab407de54d fix(jqLiteSpec): jQuery's css() getter works only for valid rules
foo.css('bogus', 'value')
foo.css('bogus') => null

so I had to change all tests to use valid css rules
2011-09-16 02:44:28 +02:00
Igor Minar 0d7fe97aff fix(jqLite): attr() should not special-case 'class' attribute
since jQuery 1.6 'class' is not treated specially, so we have to revert this fix and use className in tests instead
2011-09-16 02:44:28 +02:00
Igor Minar a2a830e227 feat (jquery): upgrade everything to jQuery 1.6.4
Closes #556
2011-09-16 02:44:27 +02:00
Igor Minar 8336f3f0ba fix(angular-mocks): fix forEach -> angular.forEach in $browser.defer.cancel 2011-09-16 01:39:03 +02:00
Di Peng e14ac2c3b0 style(bootstrap): fix some missing spaces 2011-09-13 01:02:23 +02:00
Di Peng a13653c814 refactor(angular): externalize script load order into JSON
- move all script load order into angularFiles.js
- rakefile and angular-bootstrap.js use angularFiles.js to get script orders
- gen_jstd_configs.js uses angularFiles.js to generate various jstd config files
- run gen_jstd_configs.js whenever we run server.sh

Closes #470
2011-09-13 01:02:22 +02:00
Igor Minar 8017340cd1 chore(version): preparing the 0.10.2 sneaky-seagull iteration 2011-09-12 19:23:37 +02:00
Igor Minar 17d1aef66a chore(release): cutting the angular 0.10.1 inexorable-juggernaut release 2011-09-09 01:01:46 -07:00
Igor Minar 1856f62cb1 docs(changelog): release notes for angular 0.10.1 inexorable-juggernaut 2011-09-09 01:01:05 -07:00
Kai Groner 7e1f364177 fix($location): Use encodeUriQuery instead of escape
Closes #492
2011-09-08 23:00:59 +02:00
Vojta Jina aac68bf2ba fix(scenario): temporary fix for FF6
https://bugzilla.mozilla.org/show_bug.cgi?id=684208
2011-09-08 23:00:59 +02:00
Vojta Jina 4b4292edb8 style: fix some missing semi-colons and spaces, typos 2011-09-08 23:00:59 +02:00
Vojta Jina 292d5d1421 refactor($route): remove some odd code and rename allowReload to forceReload 2011-09-08 23:00:59 +02:00
Vojta Jina 66dec77555 fix(scenario): do not navigate if click event was cancelled
This is jQuery incompatible hack.
But we were doing monkey patching there anyway...

`$(...).trigger('click')` returns an array of return values, so that scenario
runner knows, whether the event default action was cancelled.

Without this fix, scenario runner was doing navigation even if JS code called
`event.preventDefault()`.

Note, this does not work in FF6
2011-09-08 23:00:59 +02:00
Vojta Jina 8fa79066e2 doc($location): $location docs + using $location guide 2011-09-08 23:00:59 +02:00
Vojta Jina 909415d5ed feat(scenario): browserTrigger returns false if preventDefault()
https://developer.mozilla.org/en/DOM/element.dispatchEvent
dispatchEvent method returns false if at least one of the event handlers called
preventDefault(), true otherwise.

It's helpful when browserTrigger method returns this value, as we can assert,
whether the default operation was cancelled or not.
2011-09-08 23:00:59 +02:00
Vojta Jina 4421f3d435 fix(docs): update docs to reflect new $location and fix e2e tests 2011-09-08 23:00:59 +02:00
Vojta Jina 22cb600280 fix($route): update $route to reflect $location changes
* update $route to reflect new $location
* add some more unit tests to $route
* fix some other failing unit tests
* redirect overrides the url now

Breaks $route custom redirect fn has only 3 params now
2011-09-08 23:00:59 +02:00
Vojta Jina 5ba227c7cd feat($location): $location service with html5 history api support
See documentation of $location for more info

Breaks $location has no properties, only get/set methods

Closes #168
Closes #146
Closes #281
Closes #234
2011-09-08 23:00:49 +02:00
Vojta Jina f37f0ea16e feat(jqLite): add event.isDefaultPrevented() as jQuery
Chrome's Event has defaultPrevented property, but other browsers haven't.
This is workaround for other browsers - same as jQuery.
2011-09-08 20:37:28 +02:00
Vojta Jina 91ccb4ba6e feat($browser): add $browser.baseHref()
This method abstracts <base href="" /> in document.head - returns the value.
If absolute href set, it converts the href to relative.
2011-09-08 20:37:28 +02:00
Vojta Jina d0f459c56f feat($sniffer): basic implementation of browser feature testing
This only extracts our 'hashchange' event and html5 history api detection from
$browser.

Closes #400
2011-09-08 20:37:28 +02:00
Vojta Jina cbedf55641 refactor($browser): extract MockWindow, use toHaveBeenCalledOnce 2011-09-08 20:36:33 +02:00
Vojta Jina 988ed451b5 feat($browser): jQuery style url method, onUrlChange event
This is just basic implementation of $browser.url, $browser.onUrlChange methods:

$browser.url() - returns current location.href

$browser.url('/new') - set url to /new
If supported, history.pushState is used, location.href property otherwise.

$browser.url('/new', true) - replace current url with /new
If supported, history.replaceState is used, location.replace otherwise.

$browser.onUrlChange is only fired when url is changed from the browser:
- user types into address bar
- user clicks on back/forward button
- user clicks on link

It's not fired when url is changed using $browser.url()

Breaks Removed $browser.setUrl(), $browser.getUrl(), use $browser.url()
Breaks Removed $browser.onHashChange(), use $browser.onUrlChange()
2011-09-08 20:36:33 +02:00
Vojta Jina fc2f188d4d style(filter): Couple of missing spaces, semi-colons, add empty lines 2011-09-08 17:59:52 +02:00
Vojta Jina 4b1913c5ec fix(filter.currency): Return empty string for non-numbers 2011-09-08 17:59:45 +02:00
Igor Minar 06534413d3 fix(ng:options): ng:change should be called after the new val is set
Closes #547
2011-09-07 23:37:37 -07:00
Igor Minar e54909f5ef fix(docs): use window.execScript instead of window.eval on IE
IE's window.eval doesn't execute in the global context, so we have to
use window.execScript instead which works like window.eval on normal
browsers. However execScript throws an exception when an empty string is
passed in, so I created a workaround with a workaround.
2011-09-06 14:57:37 -07:00
Vojta Jina 79f2512ba7 chore(config): Remove updateView from jstd config
This should have been part of 42062dab34
2011-09-06 17:36:53 +02:00
Vojta Jina b0eb831bce fix($browser.xhr): not convert 0 status to 200 2011-09-05 23:53:53 +02:00
Igor Minar 7f0b97e02c test(jsonp): fixing jsonp e2e tests
- buzz api keeps on throttling our requests which makes our build fail
  so I'm disabling the buzz demo e2e test
- the $xhr service jsonp test was modified to use jsonp on angularjs.org
  instead of buzz api for the same reason as mentioned above
2011-09-02 16:17:05 -07:00
Igor Minar fc7f1ef6a0 chore(version.yaml): preparing the 0.10.1 inexorable-juggernaut iteration 2011-09-02 15:41:41 -07:00
91 changed files with 15136 additions and 8011 deletions
+1
View File
@@ -10,3 +10,4 @@ performance/temp*.html
*~
angular.js.tmproj
node_modules
jsTestDriver*.conf
+100 -5
View File
@@ -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
+49 -63
View File
@@ -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
+139
View File
@@ -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'
]
}
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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();
+10 -8
View File
@@ -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}}
+4 -4
View File
@@ -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>
+2 -4
View File
@@ -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">&lt;a href=""&gt; 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: `&lt;a href="/some?foo=bar"&gt;link&lt;/a&gt;`
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 &lt;base href="" /&gt; !**
### 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}
@@ -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}
+1 -1
View File
@@ -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

+7 -7
View File
@@ -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>');
});
});
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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){
+5 -1
View File
@@ -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
+6 -1
View File
@@ -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);
}
+34
View File
@@ -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
View File
@@ -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: '!'
};
});
+63 -23
View File
@@ -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>
+1 -1
View File
@@ -1 +1 @@
../../../lib/jquery/jquery-1.4.2.min.js
../../../lib/jquery/jquery.min.js
+2 -2
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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(){
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+36 -28
View File
@@ -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) {
+43
View File
@@ -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
-67
View File
@@ -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"
-63
View File
@@ -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
-50
View File
@@ -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
-10
View File
@@ -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"}
-62
View File
@@ -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
-6240
View File
File diff suppressed because it is too large Load Diff
-154
View File
@@ -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);
+9046
View File
File diff suppressed because it is too large Load Diff
+4
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
1.6.4
+12
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#!/bin/bash
node gen_jstd_configs.js
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 90000
+7 -7
View File
@@ -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('&') : '';
}
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+11 -47
View File
@@ -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);
}
+19 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
}
+1 -1
View File
@@ -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;
+29 -4
View File
@@ -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);
};
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2 -2
View File
@@ -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.
+24
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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');
+20
View File
@@ -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
View File
@@ -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');
});
});
});
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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]; }
+9
View File
@@ -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'});
-13
View File
@@ -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
View File
@@ -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
View File
@@ -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');
})
});
});
+2 -1
View File
@@ -1,3 +1,4 @@
'use strict';
var _jQuery = jQuery;
var _jQuery = jQuery,
_jqLiteMode = false;
+2 -1
View File
@@ -1,3 +1,4 @@
'use strict';
var _jQuery = jQuery.noConflict(true);
var _jQuery = jQuery.noConflict(true),
_jqLiteMode = true;
+1 -1
View File
@@ -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
View File
@@ -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");
+34 -8
View File
@@ -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() {
+1 -2
View File
@@ -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
View File
@@ -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');
});
});
});
+4 -4
View File
@@ -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
View File
@@ -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);
});
});
});
});
+38
View File
@@ -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);
});
});
+13
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
# AngularJS build config file
---
version: 0.10.0
codename: chicken-hands
version: 0.10.2
codename: sneaky-seagull