Compare commits

...

556 Commits

Author SHA1 Message Date
Igor Minar fe65dd926c chore(release): cutting the 0.10.3 shattering-heartbeat release 2011-10-14 08:31:39 -07:00
Igor Minar 669b53ede2 fix(docs): fix jsfiddle integration
this got accidentally messed up during the forms refactoring and mass renaming
2011-10-14 08:31:00 -07:00
Igor Minar b0c3f28e8f docs(forms): fix devguide forms example code 2011-10-14 08:30:02 -07:00
Igor Minar 9810dc0993 docs(cookbook): disable jsfiddle for deeplinking example
it uses extra resources, so it won't work on fiddle
2011-10-14 08:29:33 -07:00
Vojta Jina ab5df20dfa chore(libs): update libs (jasmine, jstd, jasmine-jstd adapter) 2011-10-13 17:36:11 -07:00
Igor Minar d83a92c121 fix(checkbox): prefix true-value & false-value with ng: 2011-10-13 17:35:00 -07:00
Igor Minar d0425de29e chore(release): preparing release notes for 0.10.3 2011-10-13 16:54:30 -07:00
Igor Minar ad5e42cf82 docs($location): Html5 -> HTML5 2011-10-13 14:42:49 -07:00
Igor Minar 9ed1126adb docs($location): update replace() docs 2011-10-13 14:37:37 -07:00
Igor Minar 7a19eb84aa docs($location): fix $config -> $locationConfig in docs 2011-10-13 14:07:48 -07:00
Igor Minar 718741acab chore(logo): add hi-res ng logo 2011-10-13 13:44:38 -07:00
Vojta Jina ec8bb675b4 fix(docs): set proper base href when hashbang url requested 2011-10-13 11:10:20 -07:00
Vojta Jina 8e32f3fd35 fix(nodeserver): docs rewriting
There can be url /api which does not end with / and we want to rewrite this url as well...
2011-10-13 11:10:20 -07:00
Vojta Jina 02332107e5 feat(nodeserver): if index.html exists, serve it instead of directory listing 2011-10-13 11:10:20 -07:00
Vojta Jina afc81b554e fix($location): do not rewrite link when meta key pressed 2011-10-13 11:10:19 -07:00
Vojta Jina 26e8ab3693 feat(scenario): allow key pressing when triggering browser event
Add parameter to our browserTriger function to allow specifying which keys are pressed.

Note, this does not work on IE<9 !
2011-10-13 11:10:19 -07:00
Igor Minar 28ccc76aa1 docs(dev_guide.bootstrap.auto_bootstrap): fixing a typo 2011-10-12 23:04:48 -07:00
Igor Minar b3c4cb7cff docs(*): remove @workInProgress from everywhere
it's not useful any more and it only makes the docs look ugly
2011-10-12 23:04:48 -07:00
Misko Hevery 4af4378b11 fix(forms): broken tests on jQuery and ie8&9 2011-10-12 23:04:48 -07:00
Igor Minar 8611ebe6a0 fix(compiler): linking function should call $digest
The linked scope should be $digest-ed but only if a $digest isn't
already running on it.
2011-10-12 23:04:48 -07:00
Igor Minar 8f46a3c9ac fix(jqLite): attr for boolean attribute should lowercase value 2011-10-12 23:04:47 -07:00
Igor Minar 66fdb36ecb refactor(ng:bind-attr): simplify impl by leveraging jquery 2011-10-12 23:04:47 -07:00
Igor Minar f0f5ffa9aa test(jstd-config): add widget specs to jsTestDriver-jquery.conf 2011-10-12 23:04:47 -07:00
Igor Minar 2bc7afd3ba fix(ng:class): ignore undefined or NaN classnames 2011-10-12 23:04:47 -07:00
Igor Minar a4b45397e0 docs(forms): add ng:change docs and other fixes 2011-10-12 11:49:42 -07:00
Igor Minar de4e06ed73 test(checkbox): add test for ng:change 2011-10-12 11:11:10 -07:00
Misko Hevery fd822bdaf9 chore(formating): clean code to be function() { 2011-10-11 11:01:46 -07:00
Misko Hevery 4f78fd692c feat(forms): new and improved forms 2011-10-11 11:01:45 -07:00
Misko Hevery df6d2ba326 style(examples): clean up dead examples 2011-10-11 10:53:07 -07:00
Misko Hevery ccda436f94 style(.gitignore) added IDEA 2011-10-11 10:53:07 -07:00
Misko Hevery e86c435349 refactor(bindings): remove the decoration of the DOM with errors.
Only $exceptionHandler gets notified now.
2011-10-11 10:53:07 -07:00
Misko Hevery 1942861472 refactor(hover): delete hover service 2011-10-11 10:53:06 -07:00
Misko Hevery b96e978178 fix(jqlite): removeClass would clobber class names 2011-10-11 10:53:06 -07:00
Misko Hevery bda2bba2be feat(jqlite): added .inheritedData method and $destroy event.
- refactored .scope() to use .inheritedData() instead.
- .bind('$destroy', callback) will call when the DOM element is removed
2011-10-11 10:53:05 -07:00
Misko Hevery ca08c004c8 feat(jqlite): support required as a no-value attribute 2011-10-11 10:53:05 -07:00
Misko Hevery 25a62b58db refactor(injection) infer injection args in ng:controller only
Because only controllers don't have currying, we can infer its arguments, all other APIs needing currying, automatic inference complicates the matters unecessary.
2011-10-11 10:53:04 -07:00
Misko Hevery 97e3ec4d1b style(gitignore): added xproject to ignore list 2011-10-11 10:53:04 -07:00
Misko Hevery 75f11f1fc4 feat(ng:repeat) collection items and DOM elements affinity / stability 2011-10-11 10:53:04 -07:00
Misko Hevery e134a8335f fix(filter): make json filter ignore private properties 2011-10-11 10:53:03 -07:00
Igor Minar 8ee32a75f0 chore(release): prepare the 0.10.3 shattering-heartbeat iteration 2011-10-10 11:23:08 -07:00
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
Igor Minar 8bae2a5ecb cutting the 0.10.0 chicken-hands release 2011-09-02 11:32:29 -07:00
Igor Minar 30e5f6274a docs(changelog): release notes for 0.10.0 chicken-hands 2011-09-02 10:53:41 -07:00
Igor Minar bc6e0cc954 docs(guide/tutorial/misc): sync with google docs 2011-09-02 10:44:19 -07:00
Igor Minar 8d11db0757 docs(ng:bind): fix example code 2011-09-01 16:37:08 -07:00
Igor Minar 2a8fe56997 fix(ng:class): make ng:class friendly towards other code adding/removing classes
ng:class as well as ng:class-odd and ng:class-even always reset the
class list to whatever it was before compilation, this makes it
impossible to create another directive which adds its own classes on the
element on which ng:class was applied.

the fix simply removes all classes that were added previously by
ng:class and add classes that the ng:class expression evaluates to.

we can now guarantee that we won't clobber stuff added before or after
compilation as long as all class names are unique.

in order to implement this I had to beef up jqLite#addClass and
jqLite#removeClass to be able to add/remove multiple classes without
creating duplicates.
2011-09-01 16:37:08 -07:00
Igor Minar 622c3ec974 fix(jqLite): addClass should ignore falsy values 2011-09-01 16:37:07 -07:00
Igor Minar db78aa1ce1 test(jqLite): add test for mass assignment to style 2011-09-01 16:37:07 -07:00
Igor Minar 986608fe76 fix(jqLite): special-case attr('class') because of IE9 bug 2011-09-01 16:37:06 -07:00
Igor Minar 31b8624121 feat(scope): add listener deregistration fn for $watch and $on
- both $watch and $on now return a function which when called
  deregisters the listener
- $removeListener was removed and replaced with the above
- added more tests for $watch and $on

Closes #542
2011-09-01 15:00:22 -07:00
Igor Minar a5607e3061 docs(API): various api doc fixes from Toni 2011-09-01 00:19:35 -07:00
Igor Minar ff2cb86d5d style(e2e tests): shorten jquery/jqlite describe 2011-08-31 23:03:58 -07:00
Igor Minar 825cbadf80 kill(merge): removing merge fn - dead buggy code with no specs 2011-08-31 22:32:27 -07:00
Igor Minar 93f96a16f6 fix(scope): fix edge case for $digest & $broadcast scope traversal
- fixed traversal originating on a scope with with a right sibling
- unified code for both $broadcast and $digest
2011-08-31 14:34:56 -07:00
Misko Hevery c763b009ac feat(gdocs.js): output usefull error messages when not logged in. 2011-08-31 14:34:56 -07:00
Misko Hevery e5da0c956b feat(jasmine-node): use the jasmine-node NPM to run our non-browser specs 2011-08-31 14:34:56 -07:00
Misko Hevery 3c80cf3df6 style(docs): improve the formatting of events in docs 2011-08-31 14:34:56 -07:00
Misko Hevery ad3cc16eef feat($route): add events before/after route change
BREAKING CHANGE
* removing `onChange`

FEATURE
* adding three events: $beforeRouteChange, $afterRouteChange, $routeReload
2011-08-31 14:31:23 -07:00
Misko Hevery 08d09ecbaa feat(docs): add support for events 2011-08-31 14:30:21 -07:00
Misko Hevery 456c7f62c5 bug($xhr.bulk): fix exceptions under some circumstances 2011-08-31 14:29:37 -07:00
Misko Hevery c9e7fb894b style(tests): correct indentation 2011-08-30 22:05:13 -07:00
Di Peng 4a9ccc0abc test(i18n): Update some i18n/e2e tests 2011-08-30 02:11:10 -07:00
Di Peng bceadd8e30 feat(i18n): locale specific files changed due to change in ClosureSlurper.js 2011-08-30 02:11:10 -07:00
Di Peng 545b31aa2e feat(closureSlurper): closureSlurper plural rules
- fetch plural rules from closure
- distribtue pluralization rules into each locale specific files
2011-08-30 02:11:09 -07:00
Di Peng e068addadb feat(widget): add ng:pluralize as an Angular widget 2011-08-30 02:11:09 -07:00
Di Peng 0da4902e9d feat(locale): add getPluralCat function 2011-08-30 02:11:08 -07:00
Igor Minar 3ba90003b4 fix(test): improve $cookie service test to work with Safari 5.1
the max size for safari cookies has changed sligtly so I had to adjust
the test to make cookie creation fail on this browser
2011-08-29 14:38:28 -07:00
Di Peng 5927b23ef3 fix(markup): Make special attrs such as ng:href work even without binding
- special attrs such as ng:href, ng:check did not work as intended when
their values do not contain bindings. And this commit is to fix that

Closes #534
2011-08-25 16:34:18 -07:00
Igor Minar 452607fc64 fix(events): fixing IE specific issues
IE doesn't have Array#indexOf and [].splice.call doesn't work there
either.
2011-08-24 18:36:38 -07:00
Di Peng 19401280ae feat(doc): generate both normal and debug version of index.html
- index.html has manifest file and angular.min.js
- index-jq.html has manifest file, angular.min.js and jquery.min.js
- index-debug.html has angular.js
- index-jq-debug.html has angular.js and jquery.min.js
2011-08-24 15:03:09 -07:00
Igor Minar 08a33e7bb3 feat(scope): support for events
- register listeners with $on
- remove listeners with $removeListener
- fire event that bubbles to root with $emit
- fire event that propagates to all child scopes with $broadcast
2011-08-24 15:01:50 -07:00
Igor Minar 30753cb131 feat(ng:cloak): add ng:cloak directive 2011-08-24 15:01:49 -07:00
Di Peng dbf8afcba0 fix(AngularPublic): expose missing angular public methods
- expose lowercase, uppercase and isDate to angular public api
- remove unnecessary extention of jqLite at an early stage
2011-08-24 08:00:33 -07:00
Toni Thompson 446f6b233f doc(API): various API documentation improvements 2011-08-24 11:06:55 +02:00
Igor Minar e3fad0feb3 fix(jqlite): correct the jqLite.removeClass method 2011-08-23 10:27:51 -07:00
Igor Minar 0f6e199d98 chore(ide): add pom.xml for IDEs that grok maven 2011-08-23 08:17:46 -07:00
Di Peng 15e6105779 fix(docs): remove more unecessary use of hide() and show() method
- tutorial section of docs fails to render properly as
doc:tutorial-instructions widget uses deprecated show and hide methods
of jQlite.
2011-08-21 09:40:53 -07:00
Igor Minar ee38918059 doc(changelog): add info about the memory leak in ng:options 2011-08-21 01:31:54 -07:00
Igor Minar 94e3b28d24 preparing the project for 0.10.0 chicken-hands iteration 2011-08-21 00:52:26 -07:00
Igor Minar 38e6d1e313 release notes of the 0.9.19 canine-psychokinesis 2011-08-21 00:50:58 -07:00
Igor Minar 607de75fa4 fix(docs): work around the lame ng:show directive 2011-08-21 00:46:58 -07:00
Igor Minar db04241beb fix(docs): change docs.css to avoid css clashes in buzz example 2011-08-21 00:46:46 -07:00
Di Peng 774db0aecb fix(sample): Fix for jsFiddle integration 2011-08-19 15:27:21 -07:00
Igor Minar dc0b0c77c7 feat($route): add reloadOnSearch route param to avoid reloads
In order to avoid unnecesary route reloads when just hashSearch part
of the url changes, it is now possible to disable this behavior by
setting reloadOnSearch param of the route declaration to false.

Closes #354
2011-08-19 03:55:47 -07:00
Karl Seamon 6114c8f504 fix($resource): properly call error callback when resource is called with two arguments 2011-08-18 15:07:04 -04:00
Vojta Jina b99b0a8072 feat(test): toHaveBeenCalledOnce jasmine matcher 2011-08-18 17:41:23 +02:00
dandoyon 431b748cac doc(sample): Add javascript sandbox integration (jsFiddle)
Change doc_widget.js to:

- render "edit in jsfiddle" button next to all examples
- make opt out certain examples by adding jsfiddle="false" attribute to
  doc:source element
2011-08-16 15:15:06 -07:00
Igor Minar de34ca0b64 fix(filters): fix lint warnings 2011-08-15 12:52:09 -07:00
Igor Minar 793ecb4817 refactor(jqLite): remove jqLite show/hide support
it turns out that even with our tricks, jqLite#show is not usable in
practice and definitely not on par with jQuery. so rather than
introducing half-baked apis which introduce issues, I'm removing them.

I also removed show/hide uses from docs, since they are not needed.

Breaks jqLite.hide/jqLite.show which are no longer available.
2011-08-15 12:51:41 -07:00
Igor Minar 1d45e65f4a chore(jasmine): disable 'Jasmine waiting for..' msg 2011-08-15 00:21:02 -07:00
Igor Minar 2bbef363e4 style(*): remove extra semicolons 2011-08-15 00:21:02 -07:00
Igor Minar 035ad72726 refactor(json): use angularString instead of angular.String 2011-08-15 00:21:02 -07:00
Igor Minar 9d808239b3 style(*): wrap all assignments in if statements
we commonly assign stuff in if statments like this:

if (variable = someFn()) {
  //do something with variable
}

This results in lint and IDE warnings (did you mean ==?).

It is better to be explicit about our intention and wrap the assignement
into parens:

if ((variable = someFn())) {
  //do something with variable
}

Doing so suppresses warnings + is easier to understand the intention.

I verified that the closure compiler strips the extra parens, so there
is no byte overhead for this safety practice.

We should use this style going forward...
2011-08-15 00:20:48 -07:00
Igor Minar ef01362e44 chore(.idea): remove all obsolete idea files 2011-08-15 00:15:07 -07:00
Igor Minar 6c30601ad8 feat(i18n): add i18n/locale rule set files 2011-08-14 23:44:21 -07:00
Igor Minar 37b5c5cfe9 break(date): remove support for 'long', 'longtime' date formats and 'z' flag
The support for the 'z' formatting flag was removed becase the timezone
info can't be retrieved from the browser apis (except for en-US locale
on some but not all browsers). For this reason we don't want to support
this flag at all.

Related to this, since the 'long' and 'longtime' datetime formats require
the 'z' flag in the formatting string, we are removing support for this
format as well.
2011-08-14 23:44:21 -07:00
Di Peng 966cbd4cf8 feat(i18n): collect and convert locale info from closure
- add i18n/closure directory with closure i18n files and
  update-closure.sh script to update them
- generate.sh script runs node.js scripts that extract localization
  rules from the closure library, transform them to a more suitable
format and dumps them into i18n/locale directory as angular's $locale
services
- update Rakefile to copy i18n files to build/ and pkg/ dirs
- copy i18n stuff during rake build
- e2e tests for several locales
2011-08-14 23:44:20 -07:00
Di Peng 8534b7c7c0 refactor(date,curreny,number): inject and use $locale in filters
- filter.number, filter.currency and filter.date are injected with
$locale service so that we can just swap the service to localize these
- date filter was beefed up in order to support literal strings found in
  localization rules
2011-08-14 23:44:20 -07:00
Di Peng 6802a76007 feat($locale): add default locale service for en-US 2011-08-14 23:44:20 -07:00
Di Peng 7ec8a89362 fix(directives): make ng:class-even/odd work with ng:class
Closes #508
2011-08-14 22:46:51 -07:00
Misko Hevery 34f174066f refactor(scope): non-recursive $digest method 2011-08-14 21:32:15 -07:00
Misko Hevery 530dc412c4 refactor(scope): use double-linked-list for children
Much faster $destroy operations for large ng:repeat sets.
2011-08-14 21:32:13 -07:00
Misko Hevery 3f99cdbdc3 feat(scope): $evalAsync support 2011-08-12 16:18:41 -07:00
Misko Hevery 13e7df68a6 perf(scope): re-enable statement cacheing 2011-08-12 15:48:12 -07:00
Misko Hevery 42062dab34 refactor(scope): remove $flush/$observe ng:eval/ng:eval-order 2011-08-12 15:47:47 -07:00
Misko Hevery 1c9fc1e1de fix(scope): rerun $digest from root, rather then per scope. 2011-08-12 15:47:44 -07:00
Misko Hevery 8bc7beacd8 fix(bootstrap): missing var failed strict mode boot 2011-08-12 10:19:28 -07:00
Vojta Jina 842741ee99 doc($browser): remove duplication of $browser to docs
This was causing to show up the "$browser" twice in the menu.
2011-08-10 11:51:24 +02:00
Vojta Jina 40ad543d27 doc($browser): hide $browser.notifyWhenNoOustandingRequest method
Closes #506
2011-08-10 11:50:15 +02:00
Igor Minar 06835a462a style($function): replace $function with 'function' 2011-08-06 01:54:06 -07:00
Di Peng 142cffcf64 refactor(widgets): remove input[button, submit, reset, image] and button windgets
These widgets are useless and only trigger extra $updateViews.

The only reason we had them was to support ng:change on these widgets,
but since there are no bindings present in these cases it doesn't make
sense to support ng:change here. It's likely just a leftover from
getangular.com

Breaking change: ng:change for input[button], input[submit], input[reset], input[image]
and button widgets is not supported any more
2011-08-06 01:54:05 -07:00
Di Peng 862d78c1d9 doc(xhr): add e2e test for JSONP error handling
- add e2e tests
- refactor the example by removing clear button and simplifying the code
2011-08-06 01:54:05 -07:00
Di Peng 6f8904e027 feat($browser): JSONP error handling
since we don't know if the error was due to a client error (4xx) or
server error (5xx), we leave the status code as undefined.
2011-08-06 01:54:05 -07:00
Igor Minar 4dc9e6416a doc(tutorial): updates needed for 0.9.18 rebase 2011-08-04 10:48:17 -07:00
DiPeng 4f6e947e49 fix(docs): fix qfs.read() encoding issue
- must use binary reading when using read function in q-fs module
otherwise some unicode character may be garbled.

Closes #497
2011-08-03 16:50:25 -07:00
Vojta Jina 15efbbdc1f fix($xhr.error): fix docs and add missed breaking change
$xhr.error's first argument (request) has no callback property anymore,
it's called success now...

This breaking change was introduced by b5594a773a
2011-08-02 19:01:36 +02:00
DiPeng 67a3315e1d refactor(angular): remove unnecessary parameter for slice function
- the end index for slice, if not specified, is default to the
end of the array it operates on.
2011-08-02 07:11:47 -07:00
Misko Hevery 8f0dcbab80 feat(scope): new and improved scope implementation
- Speed improvements (about 4x on flush phase)
- Memory improvements (uses no function closures)
- Break $eval into $apply, $dispatch, $flush
- Introduced $watch and $observe

Breaks angular.equals() use === instead of ==
Breaks angular.scope() does not take parent as first argument
Breaks scope.$watch() takes scope as first argument
Breaks scope.$set(), scope.$get are removed
Breaks scope.$config is removed
Breaks $route.onChange callback has not "this" bounded
2011-08-02 01:00:03 +02:00
dandoyon 1f4b417184 doc(typos): fix couple of typos in the docs
Minor documentation fixes. Should not be any code changes.
One test changed due to dependency on text in documentation.
2011-07-30 16:41:42 +02:00
Igor Minar 2d8d5aef29 prepare the 0.9.19 canine-psychokinesis iteration 2011-07-29 20:06:32 -07:00
Igor Minar eb758bc605 fix(Rakefile): index-jq.html needs to be rewritten like index.html 2011-07-29 19:35:27 -07:00
Igor Minar 761997e082 doc(release notes): small fixes for the 0.9.18 release 2011-07-29 19:34:22 -07:00
Igor Minar aacd5b672e cutting the 0.9.18 jiggling-armfat release 2011-07-29 16:30:24 -07:00
Igor Minar 8d64793717 doc(release notes): release notes for the 0.9.18 jiggling-armfat release 2011-07-29 15:27:15 -07:00
Igor Minar 908f59a5df doc(date filter): fix dashes in api docs 2011-07-29 15:00:00 -07:00
Igor Minar a45d383da2 doc(contribute): add npm & q dependencies to setup instructions 2011-07-29 13:59:36 -07:00
Igor Minar c1a681d6f4 doc(index.html): change the order of elements in the navbar
Users often don't see Tutorial and go straight to crappy Dev Guide,
changing the order should help find them right content in the right
order.
2011-07-29 13:44:55 -07:00
Igor Minar f4df421b44 doc(css): make all navbar links bold 2011-07-29 13:43:51 -07:00
Igor Minar bdef462ccc doc(started): fixing up the doc and adding link to the tutorial 2011-07-29 12:56:50 -07:00
Igor Minar a79231dea6 doc(guide): various fixes and improvements 2011-07-29 12:46:54 -07:00
Igor Minar 3e54a1b18a doc(tutorial): fixes and improvements from Toni and Ben 2011-07-29 12:40:27 -07:00
Igor Minar 4b90f65614 feat(tutorial): add an arrow to 'Workspace Reset Instructions' links
The arrow is a hint that user won't navigate to a new page, but instead
the instructions will be displayed inline.
2011-07-29 11:43:17 -07:00
Karl Seamon b5594a773a feat($xhr): add custom error callback to $xhr, $xhr.cache, $xhr.bulk, $resource
Closes #408
2011-07-27 15:21:31 -07:00
Vojta Jina f39420e7d7 style(): fix couple of missing semi-colons 2011-07-27 22:24:07 +02:00
Vojta Jina 72e46548b8 test(filter.date): fix e2e test to pass on different time zones 2011-07-27 22:18:51 +02:00
Di Peng 9dea9de449 feat(docs): add full offline support 2011-07-26 16:35:42 -07:00
Di Peng bee78a8492 feat(docs): add a changelog link to the footer 2011-07-26 14:21:13 -07:00
Di Peng f3e04fbd6a fix(ng:show/ng:hide): use jqLite.show/jqLite.hide
The previous implementation didn't handle situation when in css
something was hidden with a cascaded display:none rule and then we
wanted to show it.

Unfortunatelly our test doesn't test this scenario because it's too
complicated. :-/
2011-07-26 14:21:13 -07:00
Vojta Jina 00ea08e0ab doc(tutorial): fix navigation widget to work without jQuery
jqLite doesn't support class selectors, can find only by tag name...
2011-07-26 14:20:24 -07:00
Di Peng 31b59efa96 feat(number/currency filter): format numbers and currency using pattern
both numbers and currency need to be formatted using a generic pattern
which can be replaced for a different pattern when angular is working in
a non en-US locale

for now only en-US locale is supported, but that will change in the
future
2011-07-26 14:16:57 -07:00
Igor Minar 17251372b1 style(ng:options): fix style and some docs 2011-07-26 10:11:40 -07:00
Misko Hevery f768954f38 fix(ng:options): add support for option groups
Closes# 450
2011-07-26 10:11:06 -07:00
Misko Hevery 3237f8b995 fix(directive): ng:options to support ng:change
Closes #463
2011-07-26 09:41:44 -07:00
Misko Hevery 7802c90e13 fix(directive): ng:options to support iterating over objects
Closes #448
2011-07-26 09:41:43 -07:00
Misko Hevery c348f2cad6 fix(directive): ng:options incorrectly re-grew options on datasource change
Closes #464
2011-07-26 09:41:42 -07:00
Misko Hevery f3456dc282 fix(directive): ng:options now support binding to expression
Closes #449
2011-07-26 09:41:41 -07:00
Misko Hevery ee04141a5a style(warnings): prevent the browser from making bogus GET requests during tests 2011-07-26 09:40:29 -07:00
Misko Hevery 66fec10dc3 style(warnings): added missing semi colons 2011-07-26 09:40:29 -07:00
Misko Hevery ae75c35746 chore(jqlite): clean up dead code 2011-07-26 09:40:29 -07:00
Misko Hevery 0cf5535333 doc(ng:view): fix broken template links in docs; add scenario test. 2011-07-26 09:40:29 -07:00
Igor Minar fdd5d9471f chore(license): update license headers + add version num
- fixed copyright overnship
- updated copyright years
- added @license tag so that closure compiler preserves the header
- added version number into headers (finally!)
2011-07-22 15:49:10 -07:00
Di Peng 0782422d1f feat(angular.version): add angular.version
- placeholders are replaced with actual angular versions when doing
rake compile
2011-07-22 15:34:55 -07:00
Di Peng 8fa066190a refactor(gen-docs): use q, qq, q-fs (node modules) to write gen-docs
- re-write gendocs.js, reader.js and writer.js
- all calls are asynchronous
2011-07-20 17:33:18 -07:00
Di Peng e90b741c94 feat(gen-docs): enable caching the whole site
Generate a manifest file automatically by reading the directories.
2011-07-20 17:09:40 -07:00
Di Peng 3af1e7ca2e feat(filter.date): add support for default datetime formats in en
- add support for full,long, medium, short datetime formats in en

Breaks MMMMM. now we don't support MMMMM anymore as old implementation differs
from Unicode Locale Data format we are following.

- removed support for fullDateTime and fullTime as it means too much
trouble with full timeZone names
- added docs for the new features
2011-07-20 17:06:56 -07:00
Di Peng 0fbaa2f12a feat(TzDate): add mock "toString" method to TzDate.
- If the third param of TzDate constructor is defined, toStirng will
just return this third parameter. Otherwise, toString will still
be treated as unimplemented method
2011-07-20 16:50:44 -07:00
Igor Minar ad3b8d7bcf chore(docs/.htaccess): bundle .htaccess with docs 2011-07-19 16:58:40 -07:00
Igor Minar 3ea2416f80 Revert "fix(ng:class): preserve classes added post compilation"
This reverts commit 2428907259.

We decided to revert this because it is not bullet proof. The issue is
that we can't reliably have both angular and non-angular code in charge
of the DOM. We could work around some issues here and there, but we
can't do it reliably, so it's better not to support DOM manipulation
that happens outside of angular. There is a good chance that once we
integrate with MDVs our possition will change, but until then our
position is that only angular or angular widgets/directives can change
change DOM that was compiled.
2011-07-19 16:07:25 -07:00
Igor Minar 9636160332 doc(.defer.cancel): temporarily disable the doc 2011-07-19 14:04:50 -07:00
Igor Minar 2b2df4754d feat($browser.$defer.cancel): support canceling defered tasks 2011-07-18 14:14:19 -07:00
Igor Minar 120701b9d9 fix($browser.setUrl): make browser.setUrl more efficient
- browser should remember the last value retrieved via browser.getUrl
- browser should update window.location only if the new value is
  different from the current window.location value
2011-07-18 14:14:19 -07:00
Igor Minar fe5240732d feat(strict mode): adding strict mode flag to all js files
the flag must be in all src and test files so that we get the benefit of
running in the strict mode even in jstd

the following script was used to modify all files:

for file in `find src test -name "*.js"`; do
  echo -e "'use strict';\n" > temp.txt
  cat $file >> temp.txt
  mv temp.txt $file
done
2011-07-18 12:12:55 -07:00
Igor Minar b98c23274b feat(strict mode): turn on ECMAScript 5 strict mode
- add 'use strict'; statement to the prefix file
- configure closure compiler to use the ES5 strict mode
- strip all file-specific strict mode flags after concatination

Closes #223
2011-07-18 12:12:55 -07:00
Igor Minar 4c6d26a38f fix(strict mode): fix all issues discovered by strict mode and unit/e2e tests 2011-07-18 12:12:54 -07:00
Igor Minar c43ce91b25 chore(closure-compiler): upgrading to v20110615 2011-07-18 12:12:54 -07:00
Igor Minar b7cf7f2a79 doc(angular.annotate): properly disable doc snippet to avoid compiler warnings 2011-07-18 12:12:54 -07:00
Igor Minar ef7cf60ebd doc(misc): fixing typos in docs 2011-07-18 00:04:38 -07:00
DiPeng 7974e7eb5f refactor($browser): hide startPoll and poll methods
Breaks $browser.poll() method is moved inline to $browser.startpoll()
Breaks $browser.startpoll() method is made private
Refactor tests to reflect updated browser API

Closes #387
2011-07-18 00:04:14 -07:00
Di Peng f9b4c9da64 refactor(docs): run e2e tests with and without jquery
- e2e tests will run index.html (without jquery) and with
index-jq.html(with jquery).
- many small changes to make e2e tests work withough JQuery as we
discover problems that were previously hidden by using real JQuery.
2011-07-17 22:19:08 -07:00
Di Peng 83ac1193f2 style(jqLiteSpec): add space 2011-07-17 22:19:08 -07:00
Di Peng 7a3fdda965 feat(jqlite): added show(),hide() and eq() methods to jqlite
- add those three methods to jqlite
2011-07-17 22:19:08 -07:00
Igor Minar b4f18fc295 style(injector): remove extra semicolon 2011-07-18 04:47:39 +00:00
Igor Minar da464683aa doc(ng:include): improve the doc example to avoid confusion 2011-07-16 22:01:19 -07:00
Igor Minar a0b35161a6 fix(doc): fix all broken links 2011-07-16 01:15:37 -07:00
Igor Minar a8f4d87be5 doc(css): add '#content-list .level-4' css 2011-07-16 01:12:29 -07:00
Igor Minar 57ea8156a1 doc(ngdoc): add 'this' and 'returns' section for methods 2011-07-16 01:12:29 -07:00
Igor Minar 6289d18e61 doc(ngdoc): fix usage format for functions bolted onto services 2011-07-16 01:12:29 -07:00
Igor Minar 9e37ebe635 test(ngdoc): add test for @ngdoc function 2011-07-16 01:12:29 -07:00
Igor Minar 345c01c81b test(ngdoc): fix a typo in the @deprecated spec 2011-07-16 01:12:29 -07:00
Igor Minar 975aef2ad2 test(ngdoc): fix failing tests 2011-07-16 01:12:28 -07:00
Di Peng c863514660 doc(angular.mock.service.$browser): add xhr docs 2011-07-16 01:12:28 -07:00
Vojta Jina 86a6cc7152 chore(configs): Add missing files to jstd config
Add jstd-scenario-adapter files into jstd configs (jquery, coverage).

Remove angular.prefix, sufifix from exclude, as they don't have to be there.
They are not included, because of *.js mask.
2011-07-14 10:59:30 +02:00
Vojta Jina 8f3276bbcd chore(scripts): add test-jquery.sh for running unit tests with jQuery 2011-07-14 10:59:25 +02:00
Di Peng 2428907259 fix(ng:class): preserve classes added post compilation
- make sure ng:class preserve classes added after compilation

Closes #355
2011-07-13 16:42:38 -07:00
Igor Minar 8a8a2cf462 refactor($browser.xhr): use $browser.addJs for JSONP
There is no reason why we shouldn't reuse $browser.addJs for JSONP
requests.
2011-07-13 16:21:08 -07:00
Igor Minar 47efe44a1d fix($browser.addJs): make addJs jQuery compatible
Change addJs implementation to avoid use of jQuery because of issues
that affect angular-ie-compat.js. See inlined comment for more info.
2011-07-13 16:21:08 -07:00
Igor Minar c52e749a6e fix($browser.xhr): properly delete jsonp callbacks 2011-07-13 16:21:08 -07:00
Igor Minar 4ab3596295 fix(ie-compat): escape \ in regexp 2011-07-13 16:21:07 -07:00
Igor Minar 106674ac1e style(ie-compat): improved generated ie compat code 2011-07-13 16:21:07 -07:00
Vojta Jina 330d1a870d fix(bootstrap): Fix bootstrap on IE<8
No reason for including ie-compat in bootstrap, it's included during angularInit.

Fix including ie-compat even for production.
2011-07-13 16:21:07 -07:00
Vojta Jina 7e2e7b07b6 doc($route): fix $route example and couple of typos
Rewrite $route example a bit, as it required $location and $route services
to be eager published in the root scope.

Fix small typos in formatter and ng:options docs.
2011-07-13 11:49:16 +02:00
Vojta Jina ce80576e0b fix:jqLite: Set event.target on IE<8
IE<8's Event has not target property - it has srcElement property.
Fix that to be consistent as jQuery.
2011-07-12 23:04:46 -07:00
Vojta Jina 10da625ed9 fix:jqLite: Normalize non-existing attributes to undefined as jQuery
jqLite was returning null, but jQuery returns undefined
2011-07-12 23:04:46 -07:00
Vojta Jina 9ee9ca13da fix:jqLite: Fix binding to more events separated by space
The var eventHandler was defined outside forEach loop, so registering more
events caused calling listeners registered by the last one.

Regression:
elm.bind('click keyup', callback1);
elm.bind('click', callback2);
elm.bind('keyup', callback3);

Firing click event would have executed callback1, callback3 !
2011-07-12 23:04:46 -07:00
Igor Minar bb39d34279 test(ng:repeat): add tests for $position for small arrays/objects 2011-07-12 22:49:12 -07:00
Igor Minar e09a78438f doc:widget: improve angular.widget docs 2011-07-11 12:19:42 -07:00
Vojta Jina 1e890863e5 docs:compile: Just fixing some typos in the docs 2011-07-10 19:29:46 +02:00
Igor Minar 76a500179d docs:template: rename <angular/> to AngularJS 2011-07-08 08:24:09 -07:00
Igor Minar 7b32c71386 docs:css: remove bullet points from tutorial sidebar 2011-07-08 08:24:09 -07:00
Igor Minar 28e84ca167 docs:css: fix indentation for pre element 2011-07-08 08:24:09 -07:00
Igor Minar 41250e9cf9 prepare the jiggling-armfat iteration 2011-07-08 08:23:30 -07:00
Mårten Dolk 77ba539f63 fix:injector: make injector compatible with Rhino (HtmlUnit) 2011-07-06 16:06:54 -07:00
Igor Minar 952225f020 doc:dependencies: add /api/ prefix to dependency links 2011-07-02 10:37:04 -07:00
Igor Minar 35f9f527d3 doc:changelog: small changelog fixes 2011-07-02 08:44:50 -07:00
Igor Minar 30bd04feaa fix:exceptionHandler mock: should not specify dependencies
also added a test for this mock service
2011-07-01 18:17:54 -07:00
Igor Minar 25a77c58c1 fix:jstd.conf: explicitly specify script load order
Originally we relied on a lot of globbing, which resulted in
angular-mocks being loaded before normal services, so we never overwrote
services like $exceptionHandler with mocks. Explict definition
guarantees that we don't fall into the loading order trap, but requires
us to remember to update the jsTestDriver.conf file every time we
add/rename/remove a js file.
2011-07-01 18:17:42 -07:00
Igor Minar 75721223b5 fix:$browser mock: defer.flush() should flush all fns 2011-07-01 17:03:50 -07:00
DiPeng f606ffed4b doc:markup: wrong spelling for Vojta, all DI's fault. 2011-06-30 20:24:17 -07:00
Igor Minar b49035a8c5 preparing the 0.9.18 jiggling-armfat iteration 2011-06-30 14:29:28 -07:00
Igor Minar 86ff9dee23 docs:include: improve docs 2011-06-30 11:33:36 -07:00
Igor Minar 6dc22fe575 feat:build: better build/pkg/ dir structure
rake package now produced directory structure that can be uploaded to
the ftp server as is without manual changes
2011-06-30 10:58:14 -07:00
Igor Minar 68ab0f9b02 docs:changelog: add release date for 0.9.17 2011-06-30 10:54:24 -07:00
Igor Minar d7e0915e62 cutting the 0.9.17 vegetable-reanimation relase 2011-06-30 09:10:59 -07:00
Igor Minar b3d5d2caa9 docs:changelog: updating release notes 2011-06-30 09:09:05 -07:00
Igor Minar c02ef92630 fix:docs: fix $orderBy example and e2e test 2011-06-30 01:07:08 -07:00
Igor Minar 1e8448b9e5 fix:docs: properly distinguish between being offline and 404 2011-06-30 00:49:44 -07:00
Igor Minar c5f3a413bc feat:$xhr: provide access to $xhr header defaults
$xhr header defaults are now exposed as $xhr.defaults.headers.common and
$xhr.default.headers.<httpmethod>. This allows applications to configure
their defaults as needed.

This commit doesn't allow headers to be set per request, only per
application. Per request change would require api change, which I tried
to avoid *for now*.
2011-06-30 00:34:50 -07:00
Igor Minar d3fb5b411e fix:tests: replace angular.annotate with annotate
forgot to fix tests in 6aee2938a7
2011-06-27 22:41:49 -07:00
Di Peng 75bc59ee4b test:ng#class: added a better unit test for ng:class 2011-06-27 22:31:29 -07:00
Igor Minar 6aee2938a7 fix:testabilityPatch: remove annotate since it's not public 2011-06-27 16:46:29 -07:00
Igor Minar 2043fd43fa docs:release notes: prepare for notes for 0.9.17 release 2011-06-27 16:46:01 -07:00
Igor Minar ce4f27aa7b fix:docs: css lint fixes 2011-06-27 15:05:38 -07:00
Vojta Jina 8e915f5545 fix:$browser: Use document.createElement to create JSONP script tag
Creating <script> tags would require a lot of extra work if we want all browsers
to load and execute these scripts. We decided to not implement that in jqLite.

See #369 for more information.

Closes #369
2011-06-27 19:04:40 +02:00
Di Peng 8e6e09a4bc fix:docs: Fix sitemap generator to use doc.id instead of doc.name
doc.id should be used instead of doc.name, otherwise links are wrongly
generated
2011-06-25 08:00:43 -07:00
Igor Minar f3323ec18e fix🔁 fix ending comment tag 2011-06-23 15:13:15 -07:00
Di Peng b5a510a343 feat:filter.date: add day/month string format support
Support new date format, specifically day of week/Month of year in string
e.g. {{ someDate | data:"EEE, MMM d, yyyy" }} -> "Wed, Jul 10, 2011"

Closes #396
2011-06-23 12:06:46 -07:00
Misko Hevery 9ec45ad5c4 fix:ng:repeater - fix $position when collection size changes 2011-06-23 08:12:01 -07:00
Misko Hevery 8e880fcb77 style:ApiSpecs - clean up test names to match jasmine conventions 2011-06-23 08:12:01 -07:00
Misko Hevery b74163dc1d fix:$orderBy - return unsorted array if no predicate
Closes #399
2011-06-23 08:12:01 -07:00
Di Peng bad62d87a1 fix:scope - reintroduce support for eager services
8cad231 broke $eager services

Problem is that the injector.eager function is not invoked when a new scope
is created. Added a test to make sure service is eagerly instantiated.

Closes #403
2011-06-23 08:06:24 -07:00
Di Peng 65b6e48742 test:angular.service - add tests for $inject 2011-06-23 07:56:58 -07:00
Vojta Jina fee3717892 Update fixed docs content (guide)
Couple of typos fixed:

* indentation
* batchLogbatchLog -> batchLog
* start periodic checking
* missing brace
2011-06-21 16:23:48 +02:00
Vojta Jina d0edc11704 Fix failing unit tests in IE7 (Binder, select widget)
The fix does not change any production code, we only need to ignore couple of attributes that IE7 should not display:
* value attribute for LI
* selected attribut for SELECT

Simplified condition in compiler test, this should have been part of f9f0905f4a
2011-06-17 22:48:22 +02:00
Vojta Jina f9f0905f4a Fix compiler test for IE9
Older IEs serialize html uppercased, but IE9 does not...
Would be better to expect case insensitive, unfortuntaly jasmine does not allow
to user regexps for throw expectations.

Closes #392
2011-06-17 19:48:49 +02:00
Vojta Jina 92ebf11b2a Remove obsolete scenario folder
* moved scenario e2e tests in test/scenario/e2e folder
* remove $location test
2011-06-17 19:13:49 +02:00
Di Peng e6ee994764 Added ng:disabled, ng:checked, ng:multiple, ng:readonly, ng:selected to markup.js.
Also added coresponding descriptions  live examples and tests for each directive to be displayed on the website.

Closes #351
2011-06-16 21:17:46 -07:00
Vojta Jina c8ee00cb2b Update docs from googledocs
* fixed typo in tutorial step-10
* revert cookbook/buzz example to use googlebuzz user
2011-06-16 16:27:46 +02:00
Vojta Jina 7460a7ef61 Fix test for scenario.Application - should remove old iframe
This behavior was changed by e83465c362
So this commit should have been part of e83465c362

Also removed hiding and navigating to about:blank as it makes no sense now...
2011-06-15 22:58:57 -07:00
Igor Minar 3c87611188 docs - various doc fixes 2011-06-15 22:32:24 -07:00
Igor Minar b842642b57 docs - stripping extra new lines 2011-06-15 22:31:40 -07:00
Di Peng d428c9910e Replaced double line break with single when text is fetched from Google Docs
Closes #384
2011-06-15 09:20:22 -07:00
Di Peng f8529672f6 Updated docs/cookbook/buzz from gdocs
With couple of fixes:

* indentation + code formatting
* use ng:href, ng:src
* change UserId to misko.hevery so it will work now
2011-06-15 01:33:56 +02:00
Misko Hevery e83465c362 Fix scenario runner on IE7, IE8
* add ng:disabled, ng:checked, ng:multiple, ng:readonly, ng:selected
* fetch fixed cookbook/advancedform (use ng:disabled)
* fire keydown instead of change on input
* remove frame when test finishes
2011-06-15 01:14:22 +02:00
Vojta Jina f370be85cb Fix small typo in docs widget (tutorial instructions) 2011-06-10 18:30:55 +02:00
Vojta Jina 5dbf0cc8a2 Fix changelog url (angularjs.com -> angularjs.org) 2011-06-08 15:58:00 -07:00
Di Peng e4a00626d8 Should have replaced all instances of element('input[name=something]').val() with input('name').val()
Closes #376
2011-06-08 15:54:57 -07:00
Misko Hevery 91a34a7027 remove ng:format=index 2011-06-08 15:21:33 -07:00
Misko Hevery af285dd370 Added ng:options directive
Closes #301
2011-06-08 15:21:33 -07:00
Misko Hevery 89e001b18a Added prepend() to jqLite 2011-06-08 15:21:33 -07:00
Misko Hevery 4f2f3c9cbf Added support for deferring callbacks in time 2011-06-08 15:21:33 -07:00
Misko Hevery c1abc03cf3 Fixed indentation error 2011-06-08 15:21:33 -07:00
Misko Hevery 04a62e83bc Throw error when compiling multiple roots
Closes #338
2011-06-08 15:21:33 -07:00
Misko Hevery f9f95879f0 Added support for properties in documentation 2011-06-08 15:21:33 -07:00
Misko Hevery 8cad231bd2 Refactor injector to have invoke method for speed reasons 2011-06-08 15:21:31 -07:00
Misko Hevery 0e17ade959 Cleanup parser code to expose smaller API 2011-06-08 15:01:32 -07:00
Misko Hevery bb67ee8d28 Added HashMap 2011-06-08 15:01:32 -07:00
Misko Hevery 2a12f7dcaa Added nextUid() function for unified way of generating IDs in angular 2011-06-08 15:01:32 -07:00
Misko Hevery 20ce797906 performance improvement of single statement in parser 2011-06-08 13:49:11 -07:00
Misko Hevery cc9f1fdf38 Proper handling of special attributes in jqlite 2011-06-08 13:49:11 -07:00
Misko Hevery f243c6aeda removed jqLite warning 2011-06-08 13:49:11 -07:00
Misko Hevery 26e651996a fix JSON to match native browser behavior 2011-06-08 13:49:11 -07:00
Misko Hevery f57536ddb6 cleanup old closure directives 2011-06-08 13:49:11 -07:00
Misko Hevery 73e3f4c10c better error reporting for exceptions 2011-06-08 13:49:11 -07:00
Misko Hevery ec6d106d4a code cleanup: missing ; and indentation 2011-06-08 13:49:11 -07:00
Misko Hevery fab4ada3c8 Created a performance test harness and reporter 2011-06-08 13:49:10 -07:00
Misko Hevery d6eba8f39f Number filter would return incorrect value when fractional part had leading zeros. 2011-06-08 13:49:09 -07:00
Misko Hevery 4295b3dded Allow disabling of shell scripts without changing eclipse configuration 2011-06-08 13:46:39 -07:00
Misko Hevery a23d15ad3a Remove stray console.log statemente 2011-06-08 11:52:12 -07:00
Misko Hevery 66f3317bef preparing the 0.9.17 vegetable-reanimation iteration 2011-06-08 11:24:55 -07:00
Igor Minar a4dd9ca769 fix comment stripping 2011-06-07 16:11:01 -07:00
Vojta Jina d6e4636618 Couple of missing semi-colons 2011-06-07 16:11:01 -07:00
Vojta Jina e0ee3a0726 Update latest docs content from gdocs 2011-06-07 16:11:00 -07:00
Vojta Jina e670a812fa Remove removing old comments from gdocs
There were two places where we were removing old comments from google docs:

* gdocs.js fetching script
* docs/src/reader.js
2011-06-07 14:56:56 -07:00
Vojta Jina 517ada2662 Fix couple of failing e2e tests
The reason was recent change in docs url
2011-06-07 14:56:56 -07:00
Igor Minar 88ae927857 release notes for angular 0.9.16 weather-control 2011-06-07 14:56:01 -07:00
Igor Minar bb7228e2d9 fix ngdocSpec tests 2011-06-07 14:56:01 -07:00
Igor Minar 25b3438fd7 add appcache for docs site - caches only css/js/img resources 2011-06-07 14:56:01 -07:00
Igor Minar f21b9214e6 remove $script loader because incompatibilities with e2e runner 2011-06-07 10:39:57 -07:00
Igor Minar 80ea329d5f fix css for api reference 2011-06-07 07:44:59 -07:00
Igor Minar 6e0ff5fa96 fixing the dev guide ordering 2011-06-07 07:44:49 -07:00
Vojta Jina 4cdce2d30b Docs: Error 404 page with simple suggestion for new links 2011-06-06 23:10:30 -07:00
Igor Minar 48e69ed4d1 moving the angular.mock docs down in the api list 2011-06-06 23:10:30 -07:00
Igor Minar c35b0a7907 yet another docs batch 2011-06-06 23:10:30 -07:00
Igor Minar 805bb5bb6e fixing broken docs links 2011-06-06 23:10:30 -07:00
Kenneth R. Culp 4224e8e371 images for guide 2011-06-06 23:10:29 -07:00
Kenneth R. Culp 6319ba2ed0 remove test img file 2011-06-06 23:10:29 -07:00
Igor Minar dd44e00ddd docs css fix for content-list indentation 2011-06-06 23:10:29 -07:00
Igor Minar 6e4a501127 another batch of doc fixes from ken 2011-06-06 23:10:29 -07:00
Igor Minar c7dbe4d98b di diagram change 2011-06-06 23:10:29 -07:00
Igor Minar 3069566073 api doc fixes from ken 2011-06-06 23:10:28 -07:00
Kenneth R. Culp bd9a7b9fd7 adding all omnigraffle files for tutorial and guide images 2011-06-06 23:10:28 -07:00
Igor Minar 759e6ea8ed docs css fixes 2011-06-06 23:10:28 -07:00
Igor Minar c4989d8979 add new version of api/angular doc 2011-06-06 23:10:28 -07:00
Igor Minar af710c3ac1 fix wrapping api links into code element 2011-06-06 23:10:27 -07:00
Igor Minar e89ef35d05 make *.angularjs.org analytics work on subdomains 2011-06-06 23:10:27 -07:00
Igor Minar 2ba3c3ee57 fix getting started docs 2011-06-06 23:10:27 -07:00
Igor Minar c6dbdde6dc fix hashpath prefix for docs/sitemap/etc 2011-06-06 23:10:27 -07:00
Igor Minar f8417b48be docs app css/js/html rewrite 2011-06-06 22:52:02 -07:00
Igor Minar 7f1e2e4846 new batch of docs 2011-06-06 22:52:02 -07:00
Igor Minar 5533e48dea work in progress 2011-06-06 22:52:02 -07:00
Vojta Jina 16d0354f93 Load GA script using $script loader 2011-06-06 22:52:02 -07:00
Vojta Jina 350ee31107 Google Analytics - basic page tracking for docs 2011-06-06 22:52:02 -07:00
Vojta Jina 2da07127e4 Add doc:tutorial-instructions widget 2011-06-06 22:52:01 -07:00
Vojta Jina d05e839920 Use node's vm module instead of deprecated process.compile
So that I don't have to watch the warning all the time :-D
2011-06-06 22:52:01 -07:00
Igor Minar 22dee3e278 gdocs.js - add docos style comment stripping 2011-06-06 22:52:01 -07:00
Igor Minar bc2ca384b1 move docs/content/intro to docs/content/misc 2011-06-06 22:52:01 -07:00
Vojta Jina 2380943106 Allow "section/" links pointing to "section/index" 2011-06-06 22:52:01 -07:00
Vojta Jina 297c9b5b89 Fix wrong links in source code 2011-06-06 22:52:01 -07:00
Vojta Jina 43b2cd45f0 Allow relative links in docs
So you can use links without section when they link within the section.
2011-06-06 22:52:01 -07:00
Vojta Jina e389911a35 Change warning to log place first and then link 2011-06-06 22:52:00 -07:00
Vojta Jina fd357b4cd2 Remove trailing ws 2011-06-06 22:52:00 -07:00
Igor Minar 924ffafc51 fixing broken links 2011-06-06 22:52:00 -07:00
Vojta Jina 3616716615 Indent the API reference list 2011-06-06 22:52:00 -07:00
Vojta Jina 76df0db598 Fixed docs search input with
And couple of missing semi-colons...
2011-06-06 22:52:00 -07:00
Vojta Jina e21a868524 Fix links in docs 2011-06-06 22:52:00 -07:00
Vojta Jina 7fe46e8d7e Bit of refactoring 2011-06-06 22:51:59 -07:00
Vojta Jina 864da8b553 Couple of todos 2011-06-06 22:51:59 -07:00
Vojta Jina d62b8407cf Fixed couple of failing tests in docs
These tests were failing because of rewriting urls into new 'section' format,
i.e. angular.scope -> api.angular.scope
2011-06-06 22:51:59 -07:00
Vojta Jina 8cb84eac68 Log warning for every non existing link instead of throwing exception
Guess we don't want to stop doc generating process because of non-existing link, so just log warning and continue...
2011-06-06 22:51:59 -07:00
Vojta Jina 2e0e732cad Check whether links do exist and throw exception 2011-06-06 22:51:59 -07:00
Vojta Jina 22f9354c21 Fix sitemap include (to work on case sensitive linux) 2011-06-06 22:51:59 -07:00
Igor Minar 0c75a96917 new version of the xhr service 2011-06-06 22:51:59 -07:00
Igor Minar 14407921c7 reorder docs main menu items 2011-06-06 22:51:58 -07:00
Igor Minar 8920381b44 fix docs css 2011-06-06 22:51:58 -07:00
Igor Minar 3751f172b3 add new batch of tutorial docs and images 2011-06-06 22:51:58 -07:00
Igor Minar 3776e08db0 new docs skin - WIP 2011-06-06 22:51:58 -07:00
Igor Minar c1debfb81d fix docs landing page redirection 2011-06-06 22:51:58 -07:00
Igor Minar 0fb37b08e7 add resource merging 2011-06-06 22:51:58 -07:00
Igor Minar 8fa598fa00 remove gdocs.js workaround for extra space issue in gdocs api 2011-06-06 22:28:40 -07:00
Igor Minar 91f9efed49 gdocs.js should store files under docs/content/[collection]/ 2011-06-06 22:28:39 -07:00
Igor Minar 2094bc8f61 make docs/writer.js copyDir recursive 2011-06-06 22:28:39 -07:00
Igor Minar 9701f0735b add the tutorial collection to gdocs.js 2011-06-06 22:28:39 -07:00
Igor Minar a1f3725c0f ignore non .ngdoc files 2011-06-06 22:28:39 -07:00
Kenneth R. Culp 4ac234833a Don't forget the_end... 2011-06-06 22:28:39 -07:00
Kenneth R. Culp 9d9117384f Latest greatest tutorial udpates. 2011-06-06 22:28:38 -07:00
Igor Minar 525e444a0f temporary hack to strip all the extra chars from google docs 2011-06-06 22:28:38 -07:00
Igor Minar fd112877f8 fix link to the tutorial page 2011-06-06 22:28:38 -07:00
Igor Minar 6181ca600d new batch of tutorial docs 2011-06-06 22:28:38 -07:00
Misko Hevery 11e9572b95 Move documentation under individual headings 2011-06-06 22:28:38 -07:00
Misko Hevery b6bc6c2ddf fix syntax highlighting on the javascript 2011-06-06 22:28:37 -07:00
Misko Hevery ea6b87c24b renamed tutorial so that it would sort properly 2011-06-06 22:28:37 -07:00
Kenneth R. Culp e205bd7137 Update tutorial docs. 2011-06-06 22:28:37 -07:00
Igor Minar bd7e68f12f process only tutorial files 2011-06-06 22:28:37 -07:00
Kenneth R. Culp 0a604bdb90 Tutorial files for your perusal. 2011-06-06 22:28:37 -07:00
Igor Minar 4738d49e1c new skin WIP 2011-06-06 22:28:36 -07:00
Di Peng 9fdb09ebf8 added input#val method
Closes #237
2011-06-06 12:49:46 -07:00
Di Peng 1eebb771e3 renamed $pause to $sleep AND $wait to $pause
Closes #207
2011-06-05 12:00:45 -07:00
Di Peng 9250fce19c fixed opera date.toISOString issue
Closes #365
2011-06-03 09:44:21 -07:00
Vojta Jina 9a69677551 Fix CSS tests on Opera
Opera translates name colors to numbers (red -> #ff0000)
Other browsers like FF or Chrome translate number to rgb (#ff0000 -> rgb(255, 0, 0)
So avoiding colors in tests is probably the easiest solution...
2011-06-02 12:51:12 -07:00
Vojta Jina b6db58c647 Fix formatError for FF4 and Opera
Other browsers prepend "Error: <Exception name>" to stack, but FF4 and Opera do not.
So when formatting error we prepend it by hand, when not present...
2011-06-02 12:51:12 -07:00
Vojta Jina 4b0f2dfe0c Fix IE bug - ng:href
ng:href was producing unclickable links, as the event propagation was stopped by 'a' widget

All links in regression/issue-352.html were tested in:

* Chrome 11
* Opera 11
* Firefox 4
* IE7, IE8

Closes #352
2011-06-02 11:15:41 -07:00
Vojta Jina dad2603752 Refactor $browser's lazy start polling
+ unit tests
2011-06-02 10:50:43 -07:00
Vojta Jina 50076b571d Fix hashchange event on IE8 compatibility mode
Stupid IE8 in compatibility mode or in IE7 mode returns true for `('onhashchange' in window)`, but does not support hashchange event.

Closes #353
2011-06-02 10:50:39 -07:00
Vojta Jina aa64d37a23 Fix unit test in IE7 2011-06-02 08:55:10 -07:00
Vojta Jina 2e5199997c Rename deprecated wasCalled() -> toHaveBeenCalled() in all specs
As well as wasNotCalled(), wasCalledWith(), wasNotCalledWith()
2011-05-31 10:32:54 +02:00
Vojta Jina b2f5299e0e Normalize IE XHR bug (status code 1223 to 204)
See http://bugs.jquery.com/ticket/1450
2011-05-31 10:23:30 +02:00
Vojta Jina 805e083c24 Remove trailing white spaces from all source files
find . -name "*.js" -print | xargs sed -Ei s/[[:space:]]*$//
2011-05-19 09:43:56 -07:00
Vojta Jina 1abdc097b2 JSTD adapter for running e2e tests
Couple of changes into angular.scenario runner:
 - add autotest config (runs tests when document ready)
 - update ObjectModel (forwards events)
 - use only one ObjectModel instance for all outputters
 - expose error msg and line number in ObjectModel.Spec and ObjectModel.Step
 - fix generating spec.ids
 - fix 'html' output so that it does not mutate ObjectModel

Couple of changes into docs / generator:
 - rename copy -> copyTpl
 - move docs/static into docs/examples (to avoid conflict with jstd proxy)

Running all docs e2e tests:
========================================================
1/ compile angular-scenario, jstd-scenario-adapter
>> rake compile

2/ build docs
>> rake docs

3/ start jstd server
>> ./server-scenario.sh

4/ capture some browser

5/ run node server to serve static content
>> node ../lib/nodeserver/server.js

6/ run tests
>> ./test-scenario.sh
2011-05-19 09:43:56 -07:00
Vojta Jina 9f56af9c15 XHR should add Content-type header only for POST
Sending Content-type header causes JSTD (Jetty) proxy to change GET methods into POST.
2011-05-19 09:43:56 -07:00
Vojta Jina c5f0342ad8 Don't check url (by HEAD request) before navigateTo
Removed angular.scenario.Application.checkUrlStatus_ method and these tests:
* should call error handler if status check fails
* should perform a HEAD request to verify file existence
* should call error handler if status code is less than 200
* should call error handler if status code is greater than 299
* should call error handler if status code is greater than 299
2011-05-19 09:43:56 -07:00
Igor Minar b85e95709d fix widget example code 2011-05-13 07:15:59 -07:00
Igor Minar cc5dfaf0ab fix broken link $xhr docs 2011-05-02 21:20:36 -07:00
Igor Minar 9272a1a472 fix url validator example 2011-04-26 22:51:13 -07:00
Vojta Jina f85c82acd6 Fix some typos, missing semi-colons, etc...
This is a combination of 4 commits:
* Fix some small typos, missing semi-colons, etc.
* Fix comment for angular.scenario.SpecRunner.run method
* Fixed some missing semi-colons in cookbook
* Fixed missing semi-colon in nodeserver/server.js
2011-04-25 08:17:17 -07:00
Igor Minar 35bb19856c fix typo in scenario jsdocs 2011-04-22 23:10:07 -07:00
Igor Minar 97bdf979a1 fix Cookbook link in getting started doc 2011-04-20 15:14:45 -07:00
Craig Tataryn 64938a2e81 Added a bit more documentation to ng:autobind to explain some of the semantics 2011-04-16 14:36:47 -07:00
Anthony Lieuallen e7c22e8153 spelling fix 2011-04-15 16:01:47 -04:00
Misko Hevery b2dc2ce0b5 Have iit have higher priority then ddesrcibe 2011-04-12 14:02:21 -07:00
Igor Minar 498ad0a3be preparing the 0.9.16 weather-control iteration 2011-04-12 13:58:19 -07:00
Igor Minar cfc18efd28 cutting the 0.9.15 lethal-stutter release 2011-04-11 14:23:26 -07:00
Igor Minar 97573c3930 release notes for 0.9.15 lethal-stutter release 2011-04-11 14:23:26 -07:00
Igor Minar 8bb9f12961 fix indentation in angular.filter.html examples 2011-04-11 14:23:26 -07:00
Igor Minar ce0fbc6a77 fix e2e runner's navigateTo when url contains # fragment 2011-04-11 10:20:42 -07:00
Igor Minar 3d388498e5 add test for ng:view sync cache regression
test for 9bd2c396
2011-04-11 08:04:16 -07:00
Kenneth R. Culp 3d787ab6f4 doc fix - ng:autobind, ng:controller and more 2011-04-11 07:20:30 -07:00
Igor Minar 81063a748c fix typo in angular.widget docs 2011-04-10 14:16:08 -07:00
Igor Minar 9462b556a3 fix build for IE9
all unit tests now pass under IE9
2011-04-09 00:16:44 -07:00
Igor Minar 8e6ecd98ae fix e2e runner tests 2011-04-08 10:16:56 -07:00
Igor Minar 1d7adac7a5 fix $location service docs and examples 2011-04-08 10:03:06 -07:00
Igor Minar 72ad726efa fix e2e runner's browser.location methods
when we stopped exposing $location service on the root scope
the scenario runner was not modified to access the $location
service via $service

The following apis were affected:

- browser().location().hashSearch()
- browser().location().hashPath()
- browser().location().search()
2011-04-08 09:51:08 -07:00
Igor Minar 0d2d7025e6 use special nodeName_ impl only for IE<9
apparently IE9 is one step closer to becoming a real browser by
treating xmlns-like ("foo:") prefixes in node names as part of the
node name.

fixes:
https://groups.google.com/forum/?lnk=srg#!topic/angular/TGdrV4GsL8U
2011-04-07 14:56:57 -07:00
Igor Minar d517bcad5b improve docs for angular.Object.copy 2011-04-07 12:48:14 -07:00
Igor Minar e5419db6c7 fix indentation regexp for doc:examples 2011-04-07 12:34:34 -07:00
Igor Minar 754d2541c4 correct $resource's success callback execution
succcess callbacks should be executed for status codes in the range
of <200,300).
2011-04-04 16:04:37 -07:00
Igor Minar 9bd2c3967b revert ng:view sync caching
sync caching in ng:view must be reverted becase ng:view uses
$route.onChange to listen for changes.

$route fires all onChange events before it calls $become(Controller)
which means that if the template being included via ng:view contains
ng:controller, ng:include or other widget that create new scopes,
these scopes will be created and initialized before the parent scope
is fully initialized (happens after $become is called).

For this reason ng:view must be async.

The new scope implemenetation will resolve this issue by providing
us with an api to register one-off tasks to be executed during the
flush phase. We'll be able to compile and link the included template
safely at this time.
2011-04-04 15:35:28 -07:00
Igor Minar 38ec6519a3 prepare for the angular 0.9.15 lethal-stutter iteration 2011-04-04 14:49:48 -07:00
Igor Minar 0b8cf8539d cutting the 0.9.14 key-maker release 2011-04-01 12:26:04 -07:00
Igor Minar f109604315 release notes for the 0.9.14 key-maker iteration 2011-04-01 12:26:00 -07:00
Igor Minar ac3dbae370 upgrade jstd to 1.3.2 + improve test-coverage.sh script 2011-04-01 09:59:42 -07:00
Igor Minar cf1d365f57 fix infinite loop in elementError when working with detached elements 2011-03-31 21:48:52 -07:00
Igor Minar 78a0f41058 encode query params correctly but not too agressively 2011-03-31 21:45:28 -07:00
Igor Minar eccd9bfbb3 add much needed whitespace to jqLiteSpec.js
can we agree to put more white space into our code?

I follow there rules for specs:

- 1 blank line between sections of nontrivial it block
- 2 blank lines between it blocks
- 2 blank lines between describe blocks
- 2 blank lines between beforeEach and afterEach
- no blank line between describe and the first child it
- no blank lines between two or more closing }); lines
2011-03-31 01:42:42 -07:00
Igor Minar 2d9dd1c172 add specs for jqLite wrapping/node creation
tests cover:
- creating comment tags from a string
- creating script tag from a string
- wrapping document fragment
2011-03-31 01:20:20 -07:00
Igor Minar a2c4271128 ignore jqLite#append for doc fragment
this is needed to be compatible with jqQuery 1.5.1
2011-03-31 01:19:07 -07:00
Igor Minar 56c00800c7 fix jqLite#parent to be compatible with jQuery
our original implementation doesn't work with
document fragments on IE

- tests were added to cover missing cases
2011-03-31 01:17:34 -07:00
Igor Minar 15ec78f5ef use document fragments to grow repeaters
- unless we are repeating OPTION elements, buffer new nodes in document
  fragment and append them to the DOM in one go at the end
- for OPTION elements we have to keep on using the old way
  because of how option widget communicates with select widget
  this should be change, but that change is out of scope of this CL
- modify jqLite to support wrapping of document fragments
- fix jqLite documentation typo

This change unintentionally avoids the following webkit bug that
that affects repeater growth:
https://bugs.webkit.org/show_bug.cgi?id=57059

However the following bug affecting shrining of repeaters is still
unresolved https://bugs.webkit.org/show_bug.cgi?id=57061
2011-03-30 15:24:03 -07:00
Igor Minar a4863d5244 correct size() impl for object's w/ 'length' prop
the original implementation returned incorrect value value for
objects with 'length' property.
2011-03-30 15:24:03 -07:00
Igor Minar 96a1df192a extend size() to take ownPropsOnly param
- extend size() to take size(obj, ownPropsOnly)
- add specs for size()
- update docs to mention string support
- use size() in ng:repeat

including the hasOwnProp check for all object doesn't create
significant perf penalty:
http://jsperf.com/dedicated-code-branch-for-hasownprop
2011-03-30 15:24:03 -07:00
Igor Minar 89c25fe713 call $eval in repeater only when needed
when growing children linker calls eval for new nodes, so we need
to call it only for reused nodes.
2011-03-30 15:24:03 -07:00
Igor Minar c06c5a36b1 make xhr.cache optionally synchronous
- add `sync` flag xhr.cache
- change ng:include to use the sync flag
- change ng:view to use the sync flag

The end result is that there are fewer repaints in the browser,
which means less "blinking" that user sees.
2011-03-30 15:22:22 -07:00
Igor Minar 9985104dc0 remove weird spaces from resource mutation test 2011-03-30 15:22:14 -07:00
Anthony Lieuallen 94514a91f8 Don't mutate resource if server responded with no body
If the server provides response with no body to a resource request,
resource should not mutate the resource model in the callback.
2011-03-29 00:25:22 -07:00
Pepper Lebeck-Jobe 4da65d0e8c Fixes some links and types in the DI docs. 2011-03-28 23:40:56 -07:00
Igor Minar e1d122a4b7 encode $resource query params using encodeURIComponent 2011-03-28 23:33:46 -07:00
Igor Minar 885c3ad5dd fixing lint warnings 2011-03-28 23:15:28 -07:00
Vojta Jina 9312bed472 Added missing semi-colons
So that my eclipse stops complaining...
2011-03-28 23:04:48 -07:00
Igor Minar faf29dd047 upgrade closure compiler to version 20110322
the new version minifies our js better:

              before   |  after  | diff
-----------------------------------------
min       |  62161     | 60868   | -2.1%
min+gzip  |  25176     | 24552   | -2.5%
2011-03-26 23:22:00 -07:00
Igor Minar 510b5f3d90 renaming lib/compiler-closure to lib/closure-compiler 2011-03-26 23:21:59 -07:00
Igor Minar 1e59822df7 remove _null and _undefined
they have no significant effect on minified and gziped size. in fact
they make things worse.

file        | before     | after removal
----------------------------------------
concat      | 325415     | 325297
min         | 62070      | 62161
min + gzip  | 25187      | 25176

The bottom line is that we are getting 0.05% decrease in size after
gzip without all of the hassle of using underscores everywhere.
2011-03-26 23:19:04 -07:00
Igor Minar d95a6925cd fix broken 'downloading' links 2011-03-23 15:25:05 -07:00
Pepper Lebeck-Jobe b4d680a921 Fixes two links on the conribute page. 2011-03-22 10:18:07 -07:00
Pepper Lebeck-Jobe ff4480be65 Fixes a typo xmlsn -> xmlns 2011-03-22 10:10:55 -07:00
Anthony Lieuallen cf0513dc6f Require 'yaml' in Rakefile. 2011-03-22 10:03:27 -07:00
Igor Minar dfba8fb2e7 fixing broken angular-mocks.js 2011-03-15 16:13:11 -07:00
Igor Minar a0af13f672 preparing the 0.9.14 key-maker iteration 2011-03-13 23:15:36 -07:00
Igor Minar 69e6379d19 cutting the 0.9.13 curdling-stare release 2011-03-13 22:48:26 -07:00
Igor Minar 1094b3471e preparing release notes for the 0.9.13 curdling stare release 2011-03-13 17:34:01 -07:00
Misko Hevery 3224862a9c Stop using document write, so that we are compatible with async script loader 2011-03-11 14:16:53 -08:00
Misko Hevery 0084cb5ca4 Remove the script tag after successful JSONP request 2011-03-11 14:16:53 -08:00
Misko Hevery c578f8c3ed Added XSRF prevention logic to $xhr service 2011-03-11 14:16:53 -08:00
Misko Hevery 5b05c0de03 @require in ngdoc now takes reason for dependency 2011-03-11 14:16:52 -08:00
Misko Hevery d19c0ac6d3 Changed the $browser.xhr parameter post from optional to required 2011-03-11 14:16:52 -08:00
Misko Hevery 5343deb3da Consider all 2xx responses as OK, not just 200 2011-03-11 14:16:52 -08:00
Misko Hevery 26bad2bf87 Fixed cookies which contained unescaped '=' would not show up in cookie service. 2011-03-11 14:16:52 -08:00
Igor Minar d304b0c3df fix failing autobind test on IE 2011-03-11 09:30:53 -08:00
Igor Minar 9d5c533791 ng:autobind now optionally takes element id
so it is possible to easily compile just a part of a document.

e.g.:

<html>
  <head>
    <title>partially compiled doc</title>
    <script src="angular.js" ng:autobind="compileThis"></script>
  </head>
  <body>
    this part won't be compiled: {{1+2}}
    <div id="compileThis" ng:init="i=0" ng:click="i = i+1">
      Click count: {{i}}
    </div>
  </body>
</html>
2011-03-11 08:45:43 -08:00
Igor Minar 7414e7b533 angularJsConfig now allows ng:autobind and #autobind value to be passed in 2011-03-11 08:45:16 -08:00
Misko Hevery 5432dd289a pass undefined through fn closure for better minification 2011-03-09 13:41:16 -08:00
Misko Hevery 194b2c1ea0 Fix IE: reffering to non existent var on window, must be prefixed by window 2011-03-09 13:40:47 -08:00
Misko Hevery f7a9ea6a41 fix ie7 regression in jqLite which prevented 2011-03-08 20:23:19 -08:00
Igor Minar 627eba2b7c upgrading jasmin-jstd-adapter to 1ade1ad7cad4caadb3a3 11-03-07 2011-03-07 00:07:02 -08:00
Igor Minar 73ee5fc008 jstd r899 2011-02-18 (post 1.3.1) + fix for jasmine adapter. 2011-03-07 00:07:01 -08:00
Misko Hevery 4c762bfe5c fix orberBy documentation error 2011-03-05 22:54:41 -08:00
Igor Minar 749b3e8763 preparing the 0.9.13 curdling-stare iteration 2011-03-03 23:14:43 -08:00
Igor Minar fd409bd2df cutting the 0.9.12 thought-implanter release 2011-03-03 23:14:43 -08:00
Igor Minar e80a64883d re-eagarizing the $hover service - mea culpa 2011-03-03 23:14:43 -08:00
Igor Minar 8b2753eee2 fixing left over .scope 2011-03-03 23:14:43 -08:00
Igor Minar 02aa7978d3 fixing link in thought-implanter release notes 2011-03-03 22:59:11 -08:00
Igor Minar 5bf7ff5a3e fixing broken e2e tests 2011-03-03 13:50:12 -08:00
Igor Minar a01aa7055c preparing release notes for the 0.9.12 though-implanter release 2011-03-03 00:45:20 -08:00
Igor Minar a01d888eec adding dependency injection docs 2011-03-03 00:16:20 -08:00
Igor Minar 28800a48ad renaming guide.contribute.ngdoc to contribute.ngdoc 2011-03-02 23:19:59 -08:00
Igor Minar dea72be0cc docs cleanup and improvements
- moving 'downloading' from devguide to "misc"
- syncing the latest version of 'downloading'
- getting rid of junk files
2011-03-02 23:14:13 -08:00
Igor Minar cd139f5767 $xhr service now autodetects and strips )]}',\n
")]}\',\n" is a commonly used security prefix added to json http
responses iat google and elsewhere in order to prevent certain
cross-site attacks

$xhr service now autodetects the prefix and strips it before
deserializing the json.

the implementation should be more flexible to allow for wider range
of prefixes, but we need this one right now and can address other
usecases later.
2011-03-02 22:56:14 -08:00
Igor Minar 10a7521f0b rename devguide collection in gdocs.js to guide 2011-03-02 17:40:48 -08:00
Igor Minar 887da5684b enhancing gdocs.js to work with nested collections 2011-03-01 22:36:48 -08:00
Igor Minar 95a29d7bde additional fixes for the angular.compile docs 2011-03-01 19:08:30 -08:00
Igor Minar 945056b166 linking function should return bound scope
angular.compile()() returns {scope:scope, view:view},
this isn't useful at all and only makes tests more verbose.
Instead, this change makes the linking function return scope directly
and if anyone needs the linked dom there are two ways to do it
documented in angular.compile.

other changes:
- moved angular.compile docs to the compiler so that they are closer to
  the compiler
- fixed some typos and updated angular.compile docs with the new return
  value
2011-03-01 17:09:25 -08:00
Luther Goh 128feb2674 On some linux systems, such as Ubuntu, /bin/sh symlinks to dash, which causes issues. Symlinking to bash may be more prudent 2011-03-01 10:02:57 +08:00
Igor Minar a709dc19b8 adding an extra injector spec
- added a spec for dependency graph resolution
- also simplyfying cache presence check
2011-02-27 16:19:21 -08:00
Igor Minar 65585a2d3c $cookie factory fn should not run $eval 2011-02-27 15:54:06 -08:00
Misko Hevery edbe9d8ca8 Added delay parameter to the $defer service 2011-02-25 11:30:22 -08:00
Misko Hevery 9e67da420b Corrected an issue where properties inherited from __proto__ show up in ng:repeat.
Closses #112
2011-02-25 09:23:30 -08:00
Igor Minar 5fc2b96b97 docs.css should not inline all pragraphs, only those in lists 2011-02-23 15:52:29 -08:00
Igor Minar a7ee4a8884 gdocs.js should add dropped leading space 2011-02-22 17:42:05 -08:00
Janet Davies eea0de6db4 Rewrite of the "Contributing" section of the DevGuide 2011-02-22 17:42:00 -08:00
Igor Minar d15165f207 fixing typo in guide.overview 2011-02-22 15:49:46 -08:00
Misko Hevery c7998f5f99 add class on any namespace elments 2011-02-22 15:23:08 -08:00
Igor Minar 08e3b1edbb gdocs.js should strip trailing whitespace in imported docs 2011-02-22 15:09:44 -08:00
Kenneth R. Culp fe743e31f8 Rewrite of the overview section of the dev guide 2011-02-22 15:09:44 -08:00
Misko Hevery 55ce859998 fix documentation for ie 2011-02-22 14:48:53 -08:00
Misko Hevery 5a3c9190dc correct hashchange event registration on window 2011-02-19 20:39:46 -08:00
Misko Hevery e160944bfa fix test which fails on CI build, because the image has size 2011-02-19 20:37:17 -08:00
Igor Minar 6f8940c5d0 add missing example files for temp.html 2011-02-18 23:02:13 -08:00
Misko Hevery 87cbf9f591 Remove ng:watch
Closes#143
2011-02-18 14:22:51 -08:00
Misko Hevery fd6e5e3f31 replace smart-quotes with regular quotes 2011-02-18 14:02:21 -08:00
Misko Hevery 7d4aee31bb Auto create $inject property form the argument names. Any arg starting with $ or _ will be injected 2011-02-18 13:14:07 -08:00
Misko Hevery 7a54d2791f script for dowlnoading docs from google docs 2011-02-18 11:52:57 -08:00
Misko Hevery 65243b7d60 corrected new lines in @link 2011-02-18 11:52:57 -08:00
Igor Minar 9e30baad3f resources should not over-encode chars in url path
- added encodeUriSegment that properly encodes only those chars
  that URI RFC requires us to encode
- modified Resource to use encodeUriSegment
2011-02-17 23:06:53 -08:00
Igor Minar a070ff5ad0 make all built-in services lazy
now that we require DI everywhere, we don't need any of these
services to be eager - they get initialized when and only when
they are requested.
2011-02-17 22:58:59 -08:00
Misko Hevery c90abf057b Changed the angular.compile(element)(scope[, cloneAttachNode]) 2011-02-16 08:59:57 -05:00
Misko Hevery cdc093a463 reformated multiline trinary expressions to have a leading ?/:. 2011-02-16 08:59:57 -05:00
Misko Hevery 00cc9eb32a rewrite of JQuery lite implementation, which now better supports selected sets 2011-02-16 08:59:42 -05:00
Misko Hevery ef4bb28be1 Change API angular.compile(element)([scope], [element/true]) 2011-02-16 01:03:12 -05:00
Misko Hevery 496e6bf901 refactored quickClone to cloneNode and exposed it on jQuery 2011-02-16 00:49:16 -05:00
Misko Hevery 23b255a8b7 remove $init on scope from applying compilation template
Closes #40
2011-02-16 00:49:15 -05:00
Misko Hevery e2154cbc0b remove dom manipulation API from compiler 2011-02-16 00:48:22 -05:00
Misko Hevery 0a5c00abf8 Add public API to retrieve scope from element. 2011-02-16 00:48:22 -05:00
Misko Hevery a004d487c4 allow jquery to be declared after angular in the script loading order 2011-02-16 00:48:22 -05:00
Misko Hevery 037f30a0c9 added missing semicolons 2011-02-16 00:48:21 -05:00
Igor Minar c37bb2dc28 improving the $document docs
Closes #276
2011-02-16 00:10:47 -05:00
Anthony Lieuallen 7cf70c587e Small spelling and grammar fixes in documentation. 2011-02-16 00:04:15 -05:00
Igor Minar 1777110958 split up services into individual files
- split up services into files under src/service
- split up specs into files under test/service
- rewrite all specs so that they don't depend on one global forEach
- get rid of obsolete code and tests in ng:switch
- rename mock $log spec from "$log" to "$log mock"
2011-02-15 11:01:53 -05:00
Igor Minar d2089a1633 docs - adding cross links from directives to guide.expression 2011-02-14 11:42:58 -05:00
Igor Minar e9ce22592a $resource should encode url params with encodeURIComponent 2011-02-10 17:57:42 -08:00
Igor Minar 4f6fe1d479 adding testimonials page to docs
this is just a temporary move so that the content doesn't get lost.
in the long term we want this to be part of the main site.
2011-02-10 15:40:38 -08:00
Misko Hevery 0f19cd3625 corrected missbehaved example 2011-02-09 20:11:34 -08:00
Janet Davies c1caf2560a Update Dev Guide Downloading content (to be moved to Contributing later). 2011-02-09 11:19:30 -08:00
Kenneth R. Culp b07cc0e392 Cleanup of the Getting Started guide 2011-02-09 11:01:54 -08:00
Misko Hevery a26f192e01 added omingrafle file for docs 2011-02-09 10:30:35 -08:00
Igor Minar f943180e34 renaming fag.ngdoc to faq.ngdoc 2011-02-08 20:42:32 -08:00
Igor Minar e6a6e32c72 preparing the 0.9.11 thought-implanter iteration 2011-02-08 19:25:15 -08:00
603 changed files with 124941 additions and 20509 deletions
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/perf&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/perf.sh}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/angular.js}"/>
</launchConfiguration>
+8
View File
@@ -2,6 +2,14 @@ build/
angularjs.netrc
jstd.log
.DS_Store
gen_docs.disable
test.disable
regression/temp*.html
performance/temp*.html
.idea/workspace.xml
*~
angular.js.tmproj
node_modules
jsTestDriver*.conf
angular.xcodeproj
.idea
Generated
-67
View File
@@ -1,67 +0,0 @@
IMPORTANT: THIS IS TO CERTIFY THE RIGHT TO USE THE JETBRAINS SOFTWARE PRODUCT, GRANTED BY JETBRAINS S.R.O. UNDER THE TERMS AND CONDITIONS OF THE LICENSE AGREEMENT INCLUDED WITH THE SOFTWARE. PLEASE SAVE A COPY OF THIS EMAIL FOR FUTURE REFERENCES.
========LICENSE DETAILS========
Type: Open Source License
Reference No*: LC-93161-D352729080
Date of Issue: 3 November 2010
Expiration Date: 3 November 2011
Number of Authorized Users: not limited
* Please quote this reference when contacting JetBrains
===========LICENSEE============
Name: angularjs
Customer ID: 93161
Address:
=======SOFTWARE PRODUCT========
Product Name: WebStorm
Licensed Version: 1.0 and any new product release which is made generally available before 3 November 2011
The software is shipped electronically and is available for download from:
http://www.jetbrains.com/webstorm/download/
Your WebStorm license includes a 1-year upgrade subscription qualifying you for free upgrades to any new versions of WebStorm released by JetBrains during your upgrade subscription period starting on your license issue date and ending on 3 November 2011. To check availability of the new versions of WebStorm, please visit http://www.jetbrains.com.
For running any new version of WebStorm released by JetBrains during your upgrade subscription period, please use the included below licensing details.
You can renew your upgrade subscription before or after its expiration. Your new subscription period will start on the date following the expiration date of your current upgrade subscription.
=========INSTALLATION==========
Run WebStorm and follow the Installation Wizard's instructions. To register for use of the software or change your existing registration details, go to Help/Register menu of the software and enter the included below the User Name and License Key(s) into the registration dialog:
User Name: angularjs
===== LICENSE BEGIN =====
93161-03112010
00000jBsEx59XVlc79fV"aAqWXQ09e
jQsg5TNp5X4HGhc10LNBdu!!ejRcFG
7h3S6T09YcRWs23TH0RgaM87!HqmQo
===== LICENSE END =====
===DOCUMENTATION AND SUPPORT===
WebStorm documentation:
http://www.jetbrains.com/webstorm/documentation/
Available support resources:
http://www.jetbrains.com/support/
Technical support contact:
support@jetbrains.com
Contact for the license renewal requests:
opensource@jetbrains.com
For questions, please contact:
sales@jetbrains.com
JetBrains Sales Team
http://www.jetbrains.com
"Develop with pleasure!"
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>
-11
View File
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="ProjectRootManager" version="2" />
<component name="SvnBranchConfigurationManager">
<option name="mySupportsUserInfoFilter" value="true" />
</component>
</project>
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/angular.js.iml" filepath="$PROJECT_DIR$/.idea/angular.js.iml" />
</modules>
</component>
</project>
-68
View File
@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="USE_SAME_INDENTS" value="true" />
<option name="OTHER_INDENT_OPTIONS">
<value>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="TAB_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</value>
</option>
<ADDITIONAL_INDENT_OPTIONS fileType="js">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="jsp">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="sass">
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="xml">
<option name="INDENT_SIZE" value="4" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
<ADDITIONAL_INDENT_OPTIONS fileType="yml">
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="8" />
<option name="TAB_SIZE" value="4" />
<option name="USE_TAB_CHARACTER" value="false" />
<option name="SMART_TABS" value="false" />
<option name="LABEL_INDENT_SIZE" value="0" />
<option name="LABEL_INDENT_ABSOLUTE" value="false" />
</ADDITIONAL_INDENT_OPTIONS>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>
-15
View File
@@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="gen_docs" type="BashConfigurationType" factoryName="Bash">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/bin/bash" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/gen_docs.sh" />
<option name="PARAMETERS" value="" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
</component>
-15
View File
@@ -1,15 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="rake compile" type="BashConfigurationType" factoryName="Bash">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="INTERPRETER_PATH" value="/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="PARENT_ENVS" value="true" />
<envs />
<module name="angular.js" />
<option name="SCRIPT_NAME" value="/usr/bin/rake" />
<option name="PARAMETERS" value="compile" />
<RunnerSettings RunnerId="BashRunner" />
<ConfigurationWrapper RunnerId="BashRunner" />
<method />
</configuration>
</component>
Generated
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+10
View File
@@ -30,6 +30,16 @@
</dictionary>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value>&lt;project&gt;/.externalToolBuilders/JSTD_perf.launch</value>
</dictionary>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+574 -20
View File
@@ -1,4 +1,505 @@
<a name="0.9.11"><a/>
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
- The Latest Unstable Release: <a href="#0.10.3">0.10.3 shattering-heartbeat</a>
<a name="0.10.3"></a>
# 0.10.3 shattering-heartbeat (2011-10-13) #
## Features:
- New forms, validation, support for HTML5 input widgets. Please check out:
- [Forms overview](http://docs-next.angularjs.org/guide/dev_guide.forms)
- [form widget](http://docs-next.angularjs.org/api/angular.widget.form)
- [input widget](http://docs-next.angularjs.org/api/angular.widget.input)
- [$formFactory service](http://docs-next.angularjs.org/api/angular.service.$formFactory)
- [angular.inputType](http://docs-next.angularjs.org/api/angular.inputType)
- [commit](https://github.com/angular/angular.js/commit/4f78fd692c0ec51241476e6be9a4df06cd62fdd6)
- [ng:repeat] now has element-model affinity, which makes it more friendly to third-party code that
is not aware of angular's DOM manipulation. This is also the pre-requisite for supporting
animations.
([commit](https://github.com/angular/angular.js/commit/75f11f1fc46c35a28c0905f7316ea6779145e2fb))
## Bug Fixes:
- The select widget with [ng:options] directive now correctly displays selected option (regression
from 0.10.2).
- Fix for jqLite's removeClass, which under certain circumstances could clobber class names.
([commit](https://github.com/angular/angular.js/commit/b96e978178a6acbf048aa6db466ed845e1395445))
- Other small fixes and documentation improvements.
## Breaking Changes:
- Due to changes in how forms and validation works the following were replaced with new apis:
- `angular.formatter` - use `angular.inputType` or form's `$createWidget`
- `angular.validator` - use `angular.inputType` or form's `$createWidget`
- changes to `<input>` and `<select>` elements
- `ng:model` directive is now required for data-binding to kick in
- the `name` attribute is now optional and is used only as an alias when accessing the input
widget via the form object.
- view can't affect the model without a user interaction, so the `value` attribute of the
`<input>` element and `selected` attribute of the `<option>` element if specified in the
template is ignored.
- Removed decoration of DOM elements when:
- an exception occurs - when an exception happens, it will be passed to the $exceptionHandler
service, which can decide what to do with it.
- an input widget contains invalid input - in this case the forms validation apis can be used to
display a customized error message.
- The $hover service was removed (it was needed only for the DOM decoration described above).
<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.
## Breaking Changes
- ng:repeat no longer has ng:repeat-index property. This is because the elements now have
affinity to the underlying collection, and moving items around in the collection would move
ng:repeat-index property rendering it meaningless.
<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
- 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)
- [scopes dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.scopes)
- [scope.js source file](https://github.com/angular/angular.js/blob/master/src/Scope.js)
- breaking changes section of this changelog
- added event system to scopes (see [$on], [$emit] and [$broadcast])
- added i18n and l10n support for date, currency and number filters see [i18n] docs for more info
- added localizable [ng:pluralize] widget
- added [ng:cloak] directive for hiding uncompiled templates
## Bug Fixes
- make [ng:class] friendly towards other code adding/removing classes
([commit](https://github.com/angular/angular.js/commit/2a8fe56997fddbad673748ce02abf649a709c4ca))
- several [jqLite] bugfixes and improvements
- [ng:href], [ng:src] and friends now work properly when no expression is present in the attribute
value.
(Issue [#534](https://github.com/angular/angular.js/issues/534))
- expose missing [lowercase], [uppercase] and [isDate] APIs.
## Docs
- many (but not all just yet) api docs were proof-read and improved
## Breaking Changes:
- many scope related changes:
- $onEval is no more (use $watch with a fn as the only param if you really miss it)
- $eval without params doesn't trigger model mutation observations (use $apply/$digest instead)
- $digest propagates through the scope tree automatically (this is the desired behavior anyway)
- $watch various API changes
- scope is now the first argument passed into the $watch listener
- `this` in the $watch listener is undefined instead of current scope
- objects and arrays are watched and compared by equality and not just identity
- the initial execution of the $watch listener now executes asynchronously with respect to the
code registering it via $watch
- exceptionHandler argument is no more
- initRun argument is no more
- angular.scope does not create child scopes by taking parent as the first argument - use $new
instead
- scope.$set and scope.$get were removed, use direct property assignment instead or $eval
- $route.onChange was removed and replaced with $beforeRouteChange, $afterRouteChange and
$routeUpdate events that can be used together with the new $routeParams service
- `angular.equals()` now uses `===` instead of `==` when comparing primitives
<a name="0.9.19"></a>
# 0.9.19 canine-psychokinesis (2011-08-20) #
## Features
- added error handling support for JSONP requests (see error callback param of the [$xhr] service)
([commit](https://github.com/angular/angular.js/commit/05e2c3196c857402a9aa93837b565e0a2736af23))
- exposed http response headers in the [$xhr] and [$resource] callbacks
([commit](https://github.com/angular/angular.js/commit/4ec1d8ee86e3138fb91543ca0dca28463895c090)
contributed by Karl Seamon)
- added `reloadOnSearch` [$route] param support to prevent unnecessary controller reloads and
resulting flicker
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
## 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
(Issue [#508](https://github.com/angular/angular.js/issues/508))
- fixed error handling for resources that didn't work in certain situations
([commit](https://github.com/angular/angular.js/commit/c37bfde9eb31556ee1eb146795b0c1f1504a4a26)
contributed by Karl Seamon)
## Docs
- [jsFiddle](http://jsfiddle.net/) integration for all docs.angularjs.org examples (contributed by
Dan Doyon).
## Breaking Changes
- removed [jqLite] show/hide support. See the
[commit](https://github.com/angular/angular.js/commit/4c8eaa1eb05ba98d30ff83f4420d6fcd69045d99)
message for details. Developers should use jquery or jqLite's `css('display', 'none')` and
`css('display', 'block'/'inline'/..)` instead
<a name="0.9.18"></a>
# 0.9.18 jiggling-armfat (2011-07-29) #
### Features
- [ECMAScript 5 Strict Mode](https://developer.mozilla.org/en/JavaScript/Strict_mode) compliance
- [jqLite]
- added `show()`, `hide()` and `eq()` methods to jqlite
([commit](https://github.com/angular/angular.js/commit/7a3fdda9650a06792d9278a8cef06d544d49300f))
- added $defer.cancel to support cancelation of tasks defered via the [$defer] service
- [date] filter
- added support for `full`, `long`, `medium` and `short` date-time format flags
([commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285))
- added support for `z` flag, which stands for short string timezone identifier, e.g. PST
- internal improvements to enable localization of date filter output
- [number] filter
- internal improvements to enable localization of number filter output
- [currency] filter
- support for custom currency symbols via an optional param
- internal improvements to enable localization of number filter output
- added [angular.version] for exposing the version of the loaded angular.js file
- updated angular.js and angular.min.js file headers with angular version and shorter & updated
license info
- [ng:options]
- support binding to expression (Issue [#449](https://github.com/angular/angular.js/issues/449))
- support iterating over objects (Issue [#448](https://github.com/angular/angular.js/issues/448))
- support ng:change (Issue [#463](https://github.com/angular/angular.js/issues/463))
- support option groups (`<optgroup>`)
(Issue [#450](https://github.com/angular/angular.js/issues/450))
- [$xhr] and [$resource] support for per-request error callbacks (Issue
[#408](https://github.com/angular/angular.js/issues/408)) (contributed by Karl Seamon)
### Bug Fixes
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
[commit](https://github.com/angular/angular.js/commit/77ba539f630c57b17d71dbf1e9c5667a7eb603b7)
- `ie-compat.js` fixes and improvements related to fetching this file on the fly on legacy browsers
- [jqLite]
- fix `bind()` when binding to more events separated by space
[commit](https://github.com/angular/angular.js/commit/9ee9ca13da3883d06733637f9048a83d94e6f1f8)
- non-existing attributes should return undefined just like in jQuery
[commit](https://github.com/angular/angular.js/commit/10da625ed93511dbf5d4e61ca4e42f6f2d478959)
- set event.target for IE<8
[commit](https://github.com/angular/angular.js/commit/ce80576e0b8ac9ed5a5b1f1a4dbc2446434a0002)
- improved implementation of [ng:show] and [ng:hide] directives by using jqLite/jQuery hide and
show methods
- [ng:options]
- fix incorrect re-growing of options on datasource change
(Issue [#464](https://github.com/angular/angular.js/issues/464))
### Docs
- added full offline support for docs (click on the link in the footer of docs.angularjs.org)
- many content improvements and corrections across all docs (reference api, tutorial, dev guide)
- many small design improvements
### Other
- doubled our e2e test suite by running all angular e2e tests with jqLite in addition to jQuery
### Breaking changes
- [commit](https://github.com/angular/angular.js/commit/3af1e7ca2ee8c2acd69e5bcbb3ffc1bf51239285)
removed support for the `MMMMM` (long month name), use `MMMM` instead. This was done to align
Angular with
[Unicode Technical Standard #35](http://unicode.org/reports/tr35/#Date_Format_Patterns) used by
Closure, as well as, future DOM apis currently being proposed to w3c.
- `$xhr.error`'s `request` argument has no `callback` property anymore, use `success` instead
<a name="0.9.17"></a>
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
### New Features
- New [ng:options] directive to better bind a model to `<select>` and `<option>` elements.
- New [ng:disabled], [ng:selected], [ng:checked], [ng:multiple] and [ng:readonly] directives.
- Added support for string representation of month and day in [date] filter.
- Added support for `prepend()` to [jqLite].
- Added support for configurable HTTP header defaults for the [$xhr] service.
### Bug Fixes
- Number filter would return incorrect value when fractional part had leading zeros.
- Issue #338: Show error when template with with multiple DOM roots is being compiled.
- Issue #399: return unsorted array if no predicate.
- Fixed issues with incorrect value of $position in ng:repeat when collection size changes.
- Fixed JSONP support in [$xhr] which didn't work without jquery since v0.9.13.
### Documentation
- various small fixes and improvements
### Breaking changes
- $service now has $service.invoke for method injection ($service(self, fn) no longer works)
- injection name inference no longer supports method curry and linking functions. Both must be
explicitly specified using $inject property.
- Dynamic iteration (ng:repeat) on `<option>` elements is no longer supported. Use ng:options
- Removal of index formatter (`ng:format="index"`) since its only use was with repeated `<options>`
(see above).
- Calling [$orderBy] without a predicate now returns the original unsorted array, instead of
ordering by natural order.
<a name="0.9.16"></a>
# <angular/> 0.9.16 weather-control (2011-06-07) #
### Features
- [JsTD Scenario Adapter] for running scenario tests with jstd (from command line and in multiple
browsers)
### Documentation
- brand new template for <http://docs.angularjs.org/>
- brand new tutorial that describes how to build a typical angular app
<http://docs.angularjs.org/#!/tutorial>
- lots of new content for the dev guide (still work in progress)
<http://docs.angularjs.org/#!/guide>
### Bug Fixes
- ng:href produces unclickable links on IE7 [#352](https://github.com/angular/angular.js/issues/352)
- IE 8 in compatibility mode breaks routing [#353](https://github.com/angular/angular.js/issues/353)
- IE translates a 204 response code to 1223 [#357](https://github.com/angular/angular.js/issues/357)
- Fixed unit test in IE7 [#360](https://github.com/angular/angular.js/pull/360)
- Fixed unit tests on FF4, Opera [#364](https://github.com/angular/angular.js/pull/364)
- Fixed opera date.toISOString issue [#367](https://github.com/angular/angular.js/pull/367)
### Breaking changes
- html scenario runner requires ng:autotest script attribute to start tests automatically
([example](https://github.com/angular/angular.js/blob/master/example/personalLog/scenario/runner.html#L5))
<a name="0.9.15"></a>
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #
### Features
- IE9 support
### Bug Fixes
- reverted [ng:view] sync cache fix due to regression in the order of initialization of parent
and child controllers. (commits 9bd2c396 and 3d388498)
- [$resource] success callback is now executed whenever the http status code is `<200,300>`
### Docs
- fixed intentation code that caused some of the snippets on docs.angularjs.org to be mangled.
- many small improvements of the api docs.
<a name="0.9.14"></a>
# <angular/> 0.9.14 key-maker (2011-04-01) #
### Performance
- [ng:repeat] grows (adds children) significantly faster. (commit 15ec78f5)
- [$xhr.cache] optionally executes callbacks synchronously. (commit c06c5a36)
- [ng:view] and [ng:include] use sync [$xhr.cache]
### Bug Fixes
- Fixed [$resource] encoding of query params. (commits e1d122a4, 78a0f410)
### House cleaning
- code cleanup
- better minification (min is now 2.5% or almost 1kb smaller)
- minor documentation fixes
- JsTestDriver 1.3.2 upgrade with fixed coverage support
<a name="0.9.13"></a>
# <angular/> 0.9.13 curdling-stare (2011-03-13) #
### New Features
- Added XSRF protection for the [$xhr] service. (commit c578f8c3)
- Targeted auto-bootstrap — [ng:autobind] now takes an optional value which specifies an element id
to be compiled instead of compiling the entire html document. (commit 9d5c5337)
### Bug Fixes
- Fixed IE7 regression which prevented angular from bootstrapping in this browser.
- Cookies which contain unescaped '=' are now visible via the [$cookies] service. (commit 26bad2bf)
- [$xhr] service now executes "success" callback for all 2xx responses, not just 200.
(commit 5343deb3)
- Always remove the script tag after successful JSONP request. (commit 0084cb5c)
- Removal of all `document.write` statements to make angular compabile with async script loaders.
(commit 3224862a)
### Breaking changes
- The `post` parameter of [$browser.xhr][$browser] is now non-optional. Since everyone should be
using the [$xhr] service instead of $browser.xhr, this should not break anyone. If you do use
$browser.xhr then just add null for the post value argument where post was not passed in.
<a name="0.9.12"></a>
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
### API
- Added a delay parameter to the [$defer] service. (commit edbe9d8c)
- Added `scope()` method to [angular.element][element] (jQuery) instances to retrieve a [scope]
associated with a given DOM element. (commit 0a5c00ab)
- Added inference of DI dependencies from function signature. This feature is experimental, check
out [dependency injection][guide.di] docs. (commit 7d4aee31)
### New Features
- Angular now correctly recognizes and uses jQuery even if it was loaded after angular's script.
More info at [angular.element][element]. (commit a004d487)
- All built-in angular services are now lazy-loaded. (commit a070ff5a)
- To make styling of custom html tags created via [widgets][widget] and [directives][directive]
easier, all of these elements now contain a css class with name in form of
`<namespace>-<directive/widget name>`, e.g. `<ng:include class="ng-include">`. (commit c7998f5f)
- [$xhr] service now automatically detects and strips google-style JSON security prefix from http
responses. (commit cd139f57)
### Bug Fixes
- Rewrite of JQuery lite implementation for better supports operations on multiple nodes when
matched by a selector and remove other bugs. (commit 00cc9eb3)
- Corrected an issue where properties inherited from \_\_proto\_\_ show up in ng:repeat.
(commit 9e67da42)
- Fixed url encoding issue affecting [$resource] service. (commits e9ce2259 + 9e30baad)
- Removed `$eval()` call from the [$cookies] factory function, which was causing duplicate
instances of singleton services to be created. (commit 65585a2d)
### Docs
- New docs [contribution guidelines][contribute].
- New [description of release artifacts][downloading].
- Lots of improvements and other new content.
### Breaking changes
- Removed the `$init()` method that used to be called after compilation of a template. This should
affect only fraction of angular apps because the api was primarily being used by low level widgets
tests.
The old way of compiling the DOM element was angular.compile(element).$init(); The $init was there
to allow the users to do any work to the scope before the view would be bound. This is a left over
from not having proper MVC. The new recommended way to deal with initializing scope is to put it
in the root constructor controller. To migrate simply remove the call to $init() and move any code
you had before $init() to the root controller.
(commit 23b255a8)
- Changed [angular.compile][compile] API from `angular.compile(element[, scope])` to
`angular.compile(element)([scope], [cloneAttachFn])` (commits ef4bb28b + 945056b1)
- Removed ng:watch directives since it encourages logic in the UI. (commit 87cbf9f5)
<a name="0.9.11"></a>
# <angular/> 0.9.11 snow-maker (2011-02-08) #
### Documentation
@@ -7,36 +508,38 @@
- many, but by far not all, docs were updated, improved and cleaned up
### Features
- [`$route`](http://docs.angularjs.org/#!angular.service.$route) service now supports these
features:
- [$route] service now supports these features:
- route not found handling via `#otherwise()`
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
- setting the parent scope for scopes created by the service via `#parent()`
- reloading the current route via `#reload()`
### API
- added `angular.element(...).scope()` method to retrieve scope for a given element.
### Bug Fixes
- <option> value attribute gets clobbered when the element contains new line character(s).
- <ng:view> widget now works when nested inside an <ng:include> widget
- other various small fixes
### Breaking changes
- mock [`$browser`](http://docs.angularjs.org/#!angular.mock.service.$browser) now throws an
- mock [`$browser`](http://docs.angularjs.org/#!/api/angular.mock.service.$browser) now throws an
exception if the `flush()` method is called when there are no requests to be flushed. If you
experience `No xhr requests to be flushed!` errors in your tests, it's because you called
`$browser.xhr.flush()` unexpectedly. To make the error go away, either make sure your code makes a
request via the `$xhr` service or remove all unneeded `flush()` calls.
<a name="0.9.10"><a/>
<a name="0.9.10"></a>
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
### Features
- new [`ng:view`](http://docs.angularjs.org/#!angular.widget.ng:view) widget to simplify integration
- new [`ng:view`](http://docs.angularjs.org/#!/api/angular.widget.ng:view) widget to simplify integration
with the `$route` service
- the content of all standard HTML widgets is now being processed
(e.g. `<button>{{foo}}</button>` works now) (commit 1d7b9d56)
- new [`$log`](http://docs.angularjs.org/#!angular.mock.service.$log) and
[`$exceptionHandler`](http://docs.angularjs.org/#!angular.mock.service.$exceptionHandler) service
- new [`$log`](http://docs.angularjs.org/#!/api/angular.mock.service.$log) and
[`$exceptionHandler`](http://docs.angularjs.org/#!/api/angular.mock.service.$exceptionHandler) service
mocks now part of `angular-mocks.js` (commit f5d08963)
### Bug Fixes
@@ -51,7 +554,7 @@ with the `$route` service
- lots of improvements related to formatting of the content of docs.anguarjs.org
<a name="0.9.9"><a/>
<a name="0.9.9"></a>
# <angular/> 0.9.9 time-shift (2011-01-13) #
### Security
@@ -82,8 +585,8 @@ with the `$route` service
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
### Breaking changes
- API for accessing registered services `scope.$inject` was renamed to
[`scope.$service`](http://docs.angularjs.org/#!angular.scope.$service). (commit b2631f61)
- API for accessing registered services — `scope.$inject` — was renamed to
[`scope.$service`](http://docs.angularjs.org/#!/api/angular.scope.$service). (commit b2631f61)
- Support for `eager-published` services was removed. This change was done to make explicit
dependency declaration always required in order to allow making relatively expensive services
@@ -127,7 +630,7 @@ with the `$route` service
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
<a name="0.9.8"><a/>
<a name="0.9.8"></a>
# <angular/> 0.9.8 astral-projection (2010-12-23) #
### Docs/Getting started
@@ -141,7 +644,7 @@ with the `$route` service
- Ignore input widgets which have no name (issue #153)
<a name="0.9.7"><a/>
<a name="0.9.7"></a>
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
### Bug Fixes
@@ -160,7 +663,7 @@ with the `$route` service
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
<a name="0.9.6"><a/>
<a name="0.9.6"></a>
# <angular/> 0.9.6 night-vision (2010-12-06) #
### Security
@@ -190,7 +693,7 @@ with the `$route` service
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
<a name="0.9.5"><a/>
<a name="0.9.5"></a>
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
### Docs
@@ -200,7 +703,7 @@ with the `$route` service
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
<a name="0.9.4"><a/>
<a name="0.9.4"></a>
# <angular/> 0.9.4 total-recall (2010-11-18) #
### Docs
@@ -217,7 +720,7 @@ with the `$route` service
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
<a name="0.9.3"><a/>
<a name="0.9.3"></a>
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
### Docs
@@ -245,7 +748,7 @@ with the `$route` service
simple RegExp validator.
<a name="0.9.2"><a/>
<a name="0.9.2"></a>
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
### Docs
@@ -283,7 +786,7 @@ with the `$route` service
implements HEAD
<a name="0.9.1"><a/>
<a name="0.9.1"></a>
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
### Security
@@ -310,7 +813,7 @@ with the `$route` service
- html filter now sanitizes html content for XSS attacks which may result in different behavior
<a name="0.9.0"><a/>
<a name="0.9.0"></a>
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
### Security
@@ -340,3 +843,54 @@ with the `$route` service
### Big Thanks to Our Community Contributors
- Vojta Jina
[lowercase]: http://docs.angularjs.org/#!/api/angular.lowercase
[uppercase]: http://docs.angularjs.org/#!/api/angular.uppercase
[isDate]: http://docs.angularjs.org/#!/api/angular.isDate
[scope]: http://docs.angularjs.org/#!/api/angular.scope
[compile]: http://docs.angularjs.org/#!/api/angular.compile
[element]: http://docs.angularjs.org/#!/api/angular.element
[widget]: http://docs.angularjs.org/#!/api/angular.widget
[ng:repeat]: http://docs.angularjs.org/#!/api/angular.widget.@ng:repeat
[ng:view]: http://docs.angularjs.org/#!/api/angular.widget.ng:view
[ng:include]: http://docs.angularjs.org/#!/api/angular.widget.ng:include
[ng:options]: http://docs.angularjs.org/#!/api/angular.directive.ng:options
[ng:disabled]: http://docs.angularjs.org/#!/api/angular.directive.ng:disabled
[ng:selected]: http://docs.angularjs.org/#!/api/angular.directive.ng:selected
[ng:checked]: http://docs.angularjs.org/#!/api/angular.directive.ng:checked
[ng:multiple]: http://docs.angularjs.org/#!/api/angular.directive.ng:multiple
[ng:readonly]: http://docs.angularjs.org/#!/api/angular.directive.ng:readonly
[ng:show]: http://docs.angularjs.org/#!/api/angular.directive.ng:show
[ng:hide]: http://docs.angularjs.org/#!/api/angular.directive.ng:hide
[ng:class]: http://docs.angularjs.org/#!/api/angular.directive.ng:class
[ng:src]: http://docs.angularjs.org/#!/api/angular.directive.ng:src
[ng:href]: http://docs.angularjs.org/#!/api/angular.directive.ng:href
[$defer]: http://docs.angularjs.org/#!/api/angular.service.$defer
[$cookies]: http://docs.angularjs.org/#!/api/angular.service.$cookies
[$xhr]: http://docs.angularjs.org/#!/api/angular.service.$xhr
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.service.$xhr.cache
[$resource]: http://docs.angularjs.org/#!/api/angular.service.$resource
[$route]: http://docs.angularjs.org/#!/api/angular.service.$route
[$orderBy]: http://docs.angularjs.org/#!/api/angular.Array.orderBy
[date]: http://docs.angularjs.org/#!/api/angular.filter.date
[number]: http://docs.angularjs.org/#!/api/angular.filter.number
[currency]: http://docs.angularjs.org/#!/api/angular.filter.currency
[directive]: http://docs.angularjs.org/#!/api/angular.directive
[ng:autobind]: http://docs.angularjs.org/#!/api/angular.directive.ng:autobind
[guide.di]: http://docs.angularjs.org/#!/guide/dev_guide.di
[downloading]: http://docs.angularjs.org/#!/misc/downloading
[contribute]: http://docs.angularjs.org/#!/misc/contribute
[jqLite]: http://docs.angularjs.org/#!/api/angular.element
[angular.version]: http://docs.angularjs.org/#!/api/angular.version
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
[i18n]: http://docs-next.angularjs.org/#!/guide/dev_guide.i18n
[ng:pluralize]: http://docs-next.angularjs.org/#!/api/angular.widget.ng:pluralize
[ng:cloak]: http://docs-next.angularjs.org/#!/api/angular.directive.ng:cloak
[$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
+139 -111
View File
@@ -1,43 +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/services.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'
@@ -47,6 +12,16 @@ task :default => [:compile, :test]
desc 'Init the build workspace'
task :init do
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
v = YAML::load( File.open( 'version.yaml' ) )
match = v['version'].match(/^([^-]*)(-snapshot)?$/)
NG_VERSION = Struct.new(:full, :major, :minor, :dot, :codename).
new(match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : ''),
match[1].split('.')[0],
match[1].split('.')[1],
match[1].split('.')[2],
v['codename'])
end
@@ -61,145 +36,198 @@ 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',
]
concat = 'cat ' + deps.flatten.join(' ')
File.open(path_to('angular-scenario.js'), 'w') do |f|
f.write(%x{#{concat}})
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
f.write(gen_css('css/angular.css') + "\n")
f.write(gen_css('css/angular-scenario.css'))
end
end
desc 'Compile JSTD Scenario Adapter'
task :compile_jstd_scenario_adapter => :init do
desc 'Generate IE css js patch'
task :generate_ie_compat => :init do
css = File.open('css/angular.css', 'r') {|f| f.read }
deps = [
'src/jstd-scenario-adapter/angular.prefix',
'src/jstd-scenario-adapter/Adapter.js',
'src/jstd-scenario-adapter/angular.suffix',
]
# finds all css rules that contain backround images and extracts the rule name(s), content type of
# the image and base64 encoded image data
r = /\n([^\{\n]+)\s*\{[^\}]*background-image:\s*url\("data:([^;]+);base64,([^"]+)"\);[^\}]*\}/
concat = 'cat ' + deps.flatten.join(' ')
images = css.scan(r)
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
end
# create a js file with multipart header containing the extracted images. the entire file *must*
# be CRLF (\r\n) delimited
File.open(path_to('angular-ie-compat.js'), 'w') do |f|
f.write("/*\r\n" +
"Content-Type: multipart/related; boundary=\"_\"\r\n" +
"\r\n")
images.each_index do |idx|
f.write("--_\r\n" +
"Content-Location:img#{idx}\r\n" +
"Content-Transfer-Encoding:base64\r\n" +
"\r\n" +
images[idx][2] + "\r\n")
end
f.write("--_--\r\n" +
"*/\r\n")
# generate a css string containing *background-image rules for IE that point to the mime type
# images in the header
cssString = ''
images.each_index do |idx|
cssString += "#{images[idx][0]}{*background-image:url(\"mhtml:' + jsUri + '!img#{idx}\")}"
end
# generate a javascript closure that contains a function which will append the generated css
# string as a stylesheet to the current html document
jsString = "(function(){ \r\n" +
" var jsUri = document.location.href.replace(/\\/[^\/]+(#.*)?$/, '/') + " +
" document.getElementById('ng-ie-compat').src; \r\n" +
" var css = '#{cssString}' \r\n" +
" var s = document.createElement('style'); \r\n" +
" s.setAttribute('type', 'text/css'); \r\n" +
" if (s.styleSheet) { \r\n" +
" s.styleSheet.cssText = css; \r\n" +
" } else { \r\n" +
" s.appendChild(document.createTextNode(css)); \r\n" +
" } \r\n" +
" document.getElementsByTagName('head')[0].appendChild(s); \r\n" +
"})();\r\n"
f.write(jsString)
# TODO(vojta) use jstd configuration when implemented
# (instead of including jstd-adapter-config.js)
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
f.write("/**\r\n" +
" * Configuration for jstd scenario adapter \n */\n" +
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
end
end
desc 'Compile JavaScript'
task :compile => [:init, :compile_scenario, :generate_ie_compat] do
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
deps = [
'src/angular.prefix',
ANGULAR,
files['angularSrc'],
'src/angular.suffix',
]
File.open(path_to('angular.js'), 'w') do |f|
concat = 'cat ' + deps.flatten.join(' ')
f.write(%x{#{concat}})
content = %x{#{concat}}.
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
gsub('"NG_VERSION_MAJOR"', NG_VERSION.major).
gsub('"NG_VERSION_MINOR"', NG_VERSION.minor).
gsub('"NG_VERSION_DOT"', NG_VERSION.dot).
gsub('"NG_VERSION_CODENAME"', NG_VERSION.codename).
gsub(/^\s*['"]use strict['"];?\s*$/, ''). # remove all file-specific strict mode flags
gsub(/'USE STRICT'/, "'use strict'") # rename the placeholder in angular.prefix
f.write(content)
f.write(gen_css('css/angular.css', true))
end
%x(java -jar lib/compiler-closure/compiler.jar \
%x(java -jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to('angular.js')} \
--js_output_file #{path_to('angular.min.js')})
FileUtils.cp_r 'i18n/locale', path_to('i18n')
end
desc 'Generate docs'
task :docs do
task :docs => [:init] do
`node docs/src/gen-docs.js`
File.open(path_to('docs/.htaccess'), File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('"NG_VERSION_FULL"', NG_VERSION.full)
end
end
desc 'Create angular distribution'
task :package => [:clean, :compile, :docs] do
v = YAML::load( File.open( 'version.yaml' ) )['version']
match = v.match(/^([^-]*)(-snapshot)?$/)
version = match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : '')
tarball = "angular-#{NG_VERSION.full}.tgz"
tarball = "angular-#{version}.tgz"
pkg_dir = path_to("pkg/angular-#{version}")
pkg_dir = path_to("pkg/angular-#{NG_VERSION.full}")
FileUtils.rm_r(path_to('pkg'), :force => true)
FileUtils.mkdir_p(pkg_dir)
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular.min.js'),
path_to('angular-ie-compat.js'),
path_to('angular-scenario.js')
path_to('angular-scenario.js'),
path_to('jstd-scenario-adapter.js'),
path_to('jstd-scenario-adapter-config.js'),
].each do |src|
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{version}\\1")
dest = src.gsub(/^[^\/]+\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
FileUtils.cp(src, pkg_dir + '/' + dest)
end
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{version}"
FileUtils.cp_r path_to('i18n'), "#{pkg_dir}/i18n-#{NG_VERSION.full}"
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{NG_VERSION.full}"
File.open("#{pkg_dir}/docs-#{version}/index.html", File::RDWR) do |f|
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular.min.js', "angular-#{version}.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-#{version}/docs-scenario.html", File::RDWR) do |f|
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('angular-scenario.js', "angular-scenario-#{version}.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
f.rewind
f.write text.sub('angular-scenario.js', "angular-scenario-#{NG_VERSION.full}.js")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache.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").
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").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
%x(tar -czf #{path_to(tarball)} -C #{path_to('pkg')} .)
FileUtils.cp path_to(tarball), pkg_dir
FileUtils.mv pkg_dir, path_to(['pkg', NG_VERSION.full])
puts "Package created: #{path_to(tarball)}"
end
@@ -268,7 +296,7 @@ def gen_css(cssFile, minify = false)
css.gsub! /'/, "\\\\'"
css.gsub! /\n/, "\\n"
return %Q{document.write('<style type="text/css">#{css}</style>');}
return %Q{angular.element(document).find('head').append('<style type="text/css">#{css}</style>');}
end
+141
View File
@@ -0,0 +1,141 @@
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/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
'src/service/document.js',
'src/service/exceptionHandler.js',
'src/service/formFactory.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/widget/form.js',
'src/widget/input.js',
'src/widget/select.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/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',
'test/widget/*.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/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/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',
'test/widget/*.js',
'example/personalLog/test/*.js'
],
'jstdJqueryExclude': [
'src/angular-bootstrap.js',
'src/AngularPublic.js',
'src/scenario/angular-bootstrap.js',
'test/jquery_remove.js'
]
}
+4 -82
View File
@@ -1,87 +1,9 @@
@charset "UTF-8";
[ng\:cloak], .ng-cloak {
display: none;
}
.ng-format-negative {
color: red;
}
.ng-exception {
border: 2px solid #FF0000;
font-family: "Courier New", Courier, monospace;
font-size: smaller;
white-space: pre;
}
.ng-validation-error {
border: 2px solid #FF0000;
}
/*****************
* TIP
*****************/
#ng-callout {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 13px;
font-weight: normal;
font-family: Verdana, Arial, Helvetica, sans-serif;
vertical-align: baseline;
background: transparent;
text-decoration: none;
}
#ng-callout .ng-arrow-left{
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrSLoc/AG8FeUUIN+sGebWAnbKSJodqqlsOxJtqYooU9vvk+vcJIcTkg+QAAA7");
background-repeat: no-repeat;
background-position: left top;
position: absolute;
z-index:101;
left:-12px;
height:23px;
width:10px;
top:-3px;
}
#ng-callout .ng-arrow-right{
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrCLTcoM29yN6k9socs91e5X3EyJloipYrO4ohTMqA0Fn2XVNswJe+H+SXAAA7");
background-repeat: no-repeat;
background-position: left top;
position: absolute;
z-index:101;
height:23px;
width:11px;
top:-2px;
}
#ng-callout {
position: absolute;
z-index:100;
border: 2px solid #CCCCCC;
background-color: #fff;
}
#ng-callout .ng-content{
padding:10px 10px 10px 10px;
color:#333333;
}
#ng-callout .ng-title{
background-color: #CCCCCC;
text-align: left;
padding-left: 8px;
padding-bottom: 5px;
padding-top: 2px;
font-weight:bold;
}
/*****************
* indicators
*****************/
.ng-input-indicator-wait {
background-image: url("data:image/png;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==");
background-position: right;
background-repeat: no-repeat;
}
-15
View File
@@ -1,15 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.attrMarkup
@description
Attribute markup extends the angular compiler in a very similar way as {@link angular.markup} except
that it allows you to modify the state of the attribute text rather then the contest of a node.
<pre>
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
if (attrName == 'additional-class') {
element.addClass(attrValue);
}
});
</pre>
-53
View File
@@ -1,53 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.directive
@namespace Namespace for all directives.
@description
A directive is an HTML attribute that you can use in an existing HTML element type or in a
DOM element type that you create as {@link angular.widget}, to modify that element's
properties. You can use any number of directives per element.
For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
`<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
`1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
expression and report changes. On any change it sets the span text to the expression value.
Here's how to define {@link angular.directive.ng:bind ng:bind}:
<pre>
angular.directive('ng:bind', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
linkElement.text(value);
});
};
});
</pre>
# Directive vs. Attribute Widget
Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
attribute. So why have two different ways to do the same thing? The answer is that order
matters, but we have no control over the order in which attributes are read. To solve this
we apply attribute widget before the directive.
For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
and `ng:bind`:
<pre>
<ul ng:init="people=['mike', 'mary']">
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
</ul>
</pre>
Notice that the order of execution matters here. We need to execute
{@link angular.directive.ng:repeat ng:repeat} before we run the
{@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
could not have used directive to create this template because attributes are read in an
unspecified order and there is no way of guaranteeing that the repeater attribute would
execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
DOM element into a template.
Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
expected to do so, and so they run last.
-43
View File
@@ -1,43 +0,0 @@
@workInProgress
@ngdoc function
@name angular.element
@function
@description
Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
`angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if
jQuery is loaded or a function that wraps the element or string in angular's jQuery lite
implementation.
Real jQuery always takes precedence if it was loaded before angular.
Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows
angular to manipulate DOM. The functions implemented are usually just the basic versions of
them and might not support arguments and invocation styles.
NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
raw DOM references.
Angular's jQuery lite implements these functions:
- [addClass()](http://api.jquery.com/addClass/)
- [after()](http://api.jquery.com/after/)
- [append()](http://api.jquery.com/append/)
- [attr()](http://api.jquery.com/attr/)
- [bind()](http://api.jquery.com/bind/)
- [children()](http://api.jquery.com/children/)
- [clone()](http://api.jquery.com/clone/)
- [css()](http://api.jquery.com/css/)
- [data()](http://api.jquery.com/data/)
- [hasClass()](http://api.jquery.com/hasClass/)
- [parent()](http://api.jquery.com/parent/)
- [remove()](http://api.jquery.com/remove/)
- [removeAttr()](http://api.jquery.com/removeAttr/)
- [removeClass()](http://api.jquery.com/removeClass/)
- [removeData()](http://api.jquery.com/removeData/)
- [replaceWith()](http://api.jquery.com/replaceWith/)
- [text()](http://api.jquery.com/text/)
- [trigger()](http://api.jquery.com/trigger/)
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
@returns {Object} jQuery object.
-87
View File
@@ -1,87 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.filter
@namespace Namespace for all filters.
@description
# Overview
Filters are a standard way to format your data for display to the user. For example, you
might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
Filters allow you to do just that. In addition to transforming the data, filters also modify
the DOM. This allows the filters to for example apply css styles to the filtered output if
certain conditions were met.
# Standard Filters
The Angular framework provides a standard set of filters for common operations, including:
{@link angular.filter.currency currency}, {@link angular.filter.json json},
{@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
your own filters.
# Syntax
Filters can be part of any {@link angular.scope} evaluation but are typically used with
{{bindings}}. Filters typically transform the data to a new data type, formating the data in
the process. Filters can be chained and take optional arguments. Here are few examples:
* No filter: {{1234.5678}} => 1234.5678
* Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
significant digits.
* Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
to the right of the decimal point.
# Writing your own Filters
Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
The framework passes in the input value as the first argument to your function. Any filter
arguments are passed in as additional function arguments.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
@example
The following example filter reverses a text string. In addition, it conditionally makes the
text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
modification).
<doc:example>
<doc:source>
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
if (color) {
this.$element.css('color', color);
}
return out;
});
</script>
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
</doc:source>
<doc:scenario>
it('should reverse text', function(){
expect(binding('text|reverse')).toEqual('olleh');
input('text').enter('ABC');
expect(binding('text|reverse')).toEqual('CBA');
});
</doc:scenario>
</doc:example>
-82
View File
@@ -1,82 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.formatter
@namespace Namespace for all formats.
@description
# Overview
The formatters are responsible for translating user readable text in an input widget to a
data model stored in an application.
# Writting your own Formatter
Writing your own formatter is easy. Just register a pair of JavaScript functions with
`angular.formatter`. One function for parsing user input text to the stored form,
and one for formatting the stored data to user-visible text.
Here is an example of a "reverse" formatter: The data is stored in uppercase and in
reverse, while it is displayed in lower case and non-reversed. User edits are
automatically parsed into the internal form and data changes are automatically
formatted to the viewed form.
<pre>
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</pre>
@example
<doc:example>
<doc:source>
<script type="text/javascript">
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</script>
Formatted:
<input type="text" name="data" value="angular" ng:format="reverse"/>
<br/>
Stored:
<input type="text" name="data"/><br/>
<pre>{{data}}</pre>
</doc:source>
<doc:scenario>
it('should store reverse', function(){
expect(element('.doc-example input:first').val()).toEqual('angular');
expect(element('.doc-example input:last').val()).toEqual('RALUGNA');
this.addFutureAction('change to XYZ', function($window, $document, done){
$document.elements('.doc-example input:last').val('XYZ').trigger('change');
done();
});
expect(element('.doc-example input:first').val()).toEqual('zyx');
});
</doc:scenario>
</doc:example>
-66
View File
@@ -1,66 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.markup
@description
#Overview
Markups allow the angular compiler to transform content of DOM elements or portions of this content
into other text or DOM elements for further compilation. Markup extensions do not themselves produce
linking functions. Think of markup as a way to produce shorthand for a {@link angular.widget widget}
or a {@link angular.directive directive}.
#`{{}}` (double curly) built-in markup
`{{}}` markup is a built-in markup, which translates the enclosed expression into an
{@link angular.directive.ng:bind ng:bind} directive. It simply transforms
<pre>
{{expression}}
</pre>
to:
<pre>
<span ng:bind="expression"></span>
</pre>
For example `{{1+2}}` is easier to write and understand than `<span ng:bind="1+2"></span>`. The
expanded elements are then {@link guide.compiler compiled} normally.
# Custom markup
Let's say you want to define this shorthand for a horizontal rule: `---` for `<hr/>`.
In other words, this HTML:
<pre>
header
---
footer
</pre>
should translate to:
<pre>
header
<hr/>
footer
</pre>
Here's how the angular compiler could be extended to achieve this:
<pre>
angular.markup('---', function(text, textNode, parentElement) {
var compiler = this;
var index = text.indexOf('---');
if (index > -1) {
var before = compiler.text(text.substring(0, index));
var hr = compiler.element('hr');
var after = compiler.text(text.substring(index + 3));
textNode.after(after);
textNode.after(hr);
textNode.after(before);
textNode.remove();
}
});
</pre>
Unlike {@link angular.widget widgets} and {@link angular.directive directives}, in which the
compiler matches the name of handler function to a DOM element or attribute name, for markup the
compiler calls every markup handler for every text node, giving the handler a chance to transform
the text. The markup handler needs to find all the matches in the text.
-4
View File
@@ -1,4 +0,0 @@
@workInProgress
@ngdoc overview
@name angular
@namespace The exported angular namespace.
-175
View File
@@ -1,175 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.service
@description
# Overview
Services are substituable objects, which are wired together using dependency injection (DI).
Each service could have dependencies (other services), which are passed in constructor.
Because JS is dynamicaly typed language, dependency injection can not use static types
to identify these dependencies, so each service must explicitely define its dependencies.
This is done by `$inject` property.
# Built-in services
angular provides a set of services for common operations. These services can be overriden by custom
services if needed.
Like other core angular variables and identifiers, the built-in services always start with `$`.
* {@link angular.service.$browser $browser}
* {@link angular.service.$window $window}
* {@link angular.service.$document $document}
* {@link angular.service.$location $location}
* {@link angular.service.$log $log}
* {@link angular.service.$exceptionHandler $exceptionHandler}
* {@link angular.service.$hover $hover}
* {@link angular.service.$invalidWidgets $invalidWidgets}
* {@link angular.service.$route $route}
* {@link angular.service.$xhr $xhr}
* {@link angular.service.$xhr.error $xhr.error}
* {@link angular.service.$xhr.bulk $xhr.bulk}
* {@link angular.service.$xhr.cache $xhr.cache}
* {@link angular.service.$resource $resource}
* {@link angular.service.$cookies $cookies}
* {@link angular.service.$cookieStore $cookieStore}
# Writing your own custom services
angular provides only set of basic services, so for any nontrivial application it will be necessary
to write one or more custom services. To do so, a factory function that creates a services needs to
be registered with angular's dependency injector. This factory function must return an object - the
service (it is not called with the `new` operator).
**angular.service** accepts three parameters:
- `{string} name` - Name of the service.
- `{function()} factory` - Factory function (called just once by DI).
- `{Object} config` - Configuration object with following properties:
- `$inject` - {Array.<string>} - Array of service ids that this service depends on. These
services will be passed as arguments into the factory function in the same order as specified
in the `$inject` array. Defaults to `[]`.
- `$eager` - {boolean} - If true, the service factory will be called and thus, the service will
be instantiated when angular boots. If false, service will be lazily instantiated when it is
first requested during instantiation of a dependant. Defaults to `false`.
The `this` of the factory function is bound to the root scope of the angular application.
angular enables services to participate in dependency injection (DI) by registering themselves with
angular's DI system (injector) under a `name` (id) as well as by declaring dependencies which need
to be provided for the factory function of the registered service. The ability to swap dependencies
for mocks/stubs/dummies in tests allows for services to be highly testable.
Here is an example of very simple service. This service requires $window service (it's
passed as a parameter to factory function) and it's just a function.
This service simple stores all notifications and after third one, it displays all of them by
window alert.
<pre>
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
</pre>
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
});
</pre>
# Injecting services into controllers
Using services as dependencies for controllers is very similar to using them as dependencies for
another service.
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
static types (like in static typed languages). Therefore you must specify the service name
by the `$inject` property - it's an array that contains strings with names of services to be
injected. The name must match the id that service has been registered as with angular.
The order of the services in the array matters, because this order will be used when calling
the factory function with injected parameters. The names of parameters in factory function
don't matter, but by convention they match the service ids.
<pre>
function myController($loc, $log) {
this.firstMethod = function() {
// use $location service
$loc.setHash();
};
this.secondMethod = function() {
// use $log service
$log.info('...');
};
}
// which services to inject ?
myController.$inject = ['$location', '$log'];
</pre>
@example
<doc:example>
<doc:source>
<script type="text/javascript">
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
function myController(notifyService) {
this.callNotify = function(msg) {
notifyService(msg);
};
}
myController.$inject = ['notify'];
</script>
<div ng:controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng:init="message='test'" type="text" name="message" />
<button ng:click="callNotify(message);">NOTIFY</button>
</div>
</doc:source>
<doc:scenario>
it('should test service', function(){
expect(element(':input[name=message]').val()).toEqual('test');
});
</doc:scenario>
</doc:example>
-77
View File
@@ -1,77 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.validator
@namespace Namespace for all filters.
@description
# Overview
Validators are a standard way to check the user input against a specific criteria. For
example, you might need to check that an input field contains a well-formed phone number.
# Syntax
Attach a validator on user input widgets using the `ng:validate` attribute.
<doc:example>
<doc:source>
Change me: <input type="text" name="number" ng:validate="integer" value="123">
</doc:source>
<doc:scenario>
it('should validate the default number string', function() {
expect(element('input[name=number]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate "foo"', function() {
input('number').enter('foo');
expect(element('input[name=number]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
# Writing your own Validators
Writing your own validator is easy. To make a function available as a
validator, just define the JavaScript function on the `angular.validator`
object. <angular/> passes in the input to validate as the first argument
to your function. Any additional validator arguments are passed in as
additional arguments to your function.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
In this example we have written a upsTrackingNo validator.
It marks the input text "valid" only when the user enters a well-formed
UPS tracking number.
@css ng-validation-error
When validation fails, this css class is applied to the binding, making its borders red by
default.
@example
<doc:example>
<doc:source>
<script>
angular.validator('upsTrackingNo', function(input, format) {
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
return input.match(regexp)?"":"The format must match " + format;
});
</script>
<input type="text" name="trackNo" size="40"
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
value="1Z 123 456 78 9012 345 6"/>
</doc:source>
<doc:scenario>
it('should validate correct UPS tracking number', function() {
expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate in correct UPS tracking number', function() {
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
-78
View File
@@ -1,78 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.widget
@namespace Namespace for all widgets.
@description
# Overview
Widgets allow you to create DOM elements that the browser doesn't
already understand. You create the widget in your namespace and
assign it behavior. You can only bind one widget per DOM element
(unlike directives, in which you can use any number per DOM
element). Widgets are expected to manipulate the DOM tree by
adding new elements whereas directives are expected to only modify
element properties.
Widgets come in two flavors: element and attribute.
# Element Widget
Let's say we would like to create a new element type in the
namespace `my` that can watch an expression and alert() the user
with each new value.
<pre>
&lt;my:watch exp="name"/&gt;
</pre>
You can implement `my:watch` like this:
<pre>
angular.widget('my:watch', function(compileElement) {
var compiler = this;
var exp = compileElement.attr('exp');
return function(linkElement) {
var currentScope = this;
currentScope.$watch(exp, function(value){
alert(value);
}};
};
});
</pre>
# Attribute Widget
Let's implement the same widget, but this time as an attribute
that can be added to any existing DOM element.
<pre>
&lt;div my-watch="name"&gt;text&lt;/div&gt;
</pre>
You can implement `my:watch` attribute like this:
<pre>
angular.widget('@my:watch', function(expression, compileElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value){
alert(value);
});
};
});
</pre>
@example
<doc:example>
<doc:source>
<script>
angular.widget('my:time', function(compileElement){
compileElement.css('display', 'block');
return function(linkElement){
function update(){
linkElement.text('Current time is: ' + new Date());
setTimeout(update, 1000);
}
update();
};
});
</script>
<my:time></my:time>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
+92
View File
@@ -0,0 +1,92 @@
@ngdoc overview
@name angular.inputType
@description
Angular {@link guide/dev_guide.forms forms} allow you to build complex widgets. However for
simple widget which are based on HTML input text element a simpler way of providing the validation
and parsing is also provided. `angular.inputType` is a short hand for creating a widget which
already has the DOM listeners and `$render` method supplied. The only thing which needs to
be provided by the developer are the optional `$validate` listener and
`$parseModel` or `$parseModel` methods.
All `inputType` widgets support:
- CSS classes:
- **`ng-valid`**: when widget is valid.
- **`ng-invalid`**: when widget is invalid.
- **`ng-pristine`**: when widget has not been modified by user action.
- **`ng-dirty`**: when has been modified do to user action.
- Widget properties:
- **`$valid`**: When widget is valid.
- **`$invalid`**: When widget is invalid.
- **`$pristine`**: When widget has not been modified by user interaction.
- **`$dirty`**: When user has been modified do to user interaction.
- **`$required`**: When the `<input>` element has `required` attribute. This means that the
widget will have `REQUIRED` validation error if empty.
- **`$disabled`**: When the `<input>` element has `disabled` attribute.
- **`$readonly`**: When the `<input>` element has `readonly` attribute.
- Widget Attribute Validators:
- **`required`**: Sets `REQUIRED` validation error key if the input is empty
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
patterns defined as scope expressions.
# Example
<doc:example>
<doc:source>
<script>
angular.inputType('json', function() {
this.$parseView = function() {
try {
this.$modelValue = angular.fromJson(this.$viewValue);
if (this.$error.JSON) {
this.$emit('$valid', 'JSON');
}
} catch (e) {
this.$emit('$invalid', 'JSON');
}
}
this.$parseModel = function() {
this.$viewValue = angular.toJson(this.$modelValue);
}
});
function Ctrl() {
this.data = {
framework:'angular',
codenames:'supper-powers'
}
this.required = false;
this.disabled = false;
this.readonly = false;
}
</script>
<div ng:controller="Ctrl">
<form name="myForm">
<input type="json" ng:model="data" size="80"
ng:required="{{required}}" ng:disabled="{{disabled}}"
ng:readonly="{{readonly}}"/><br/>
Required: <input type="checkbox" ng:model="required"> <br/>
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
<pre>data={{data}}</pre>
<pre>myForm={{myForm}}</pre>
</form>
</div>
</doc:source>
<doc:scenario>
it('should invalidate on wrong input', function() {
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
input('data').enter('{}');
expect(binding('data')).toEqual('data={\n }');
input('data').enter('{');
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
});
</doc:scenario>
</doc:example>
+25
View File
@@ -0,0 +1,25 @@
@ngdoc overview
@name angular.service
@description
The services API provides objects for carrying out common web app tasks. Service objects are
managed by angular's {@link guide/dev_guide.di dependency injection system}.
* {@link angular.service.$browser $browser } - Provides an instance of a browser object
* {@link angular.service.$cookieStore $cookieStore } - Provides key / value storage backed by
session cookies
* {@link angular.service.$cookies $cookies } - Provides read / write access to browser cookies
* {@link angular.service.$defer $defer } - Defers function execution and try / catch block
* {@link angular.service.$document $document } - Provides reference to `window.document` element
* {@link angular.service.$exceptionHandler $exceptionHandler } - Receives uncaught angular
exceptions
* {@link angular.service.$location $location } - Parses the browser location URL
* {@link angular.service.$log $log } - Provides logging service
* {@link angular.service.$resource $resource } - Creates objects for interacting with RESTful
server-side data sources
* {@link angular.service.$route $route } - Provides deep-linking services
* {@link angular.service.$window $window } - References the browsers `window` object
* {@link angular.service.$xhr $xhr} - Generates an XHR request.
For information on how angular services work and how to write your own services, see {@link
guide/dev_guide.services Angular Services} in the angular Developer Guide.
+72
View File
@@ -0,0 +1,72 @@
@ngdoc overview
@name API Reference
@description
## Angular Compiler API
* {@link angular.widget Widgets} - Angular custom DOM element
* {@link angular.directive Directives} - Angular DOM element attributes
* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup}
* {@link angular.filter Filters} - Angular output filters
* {@link angular.compile angular.compile()} - Template compiler
## Angular Scope API
* {@link angular.scope Scope Object} - Angular scope object
## Angular Services & Dependency Injection API
* {@link angular.service Angular Services}
* {@link angular.injector angular.injector() }
## Angular Testing API
* {@link angular.mock Testing Mocks API} - Mock objects for testing
* {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en_US
Angular Scenario Runner} - Automated scenario testing documentation
## Angular Utility Functions
### HTML & DOM Manipulation
* {@link angular.element angular.element()}
### Misc
* {@link angular.bind angular.bind() }
* {@link angular.extend angular.extend() }
* {@link angular.forEach angular.forEach() }
* {@link angular.identity angular.identity() }
* {@link angular.noop angular.noop() }
## Type Identification
* {@link angular.isArray angular.isArray() }
* {@link angular.isDate angular.isDate() }
* {@link angular.isDefined angular.isDefined() }
* {@link angular.isFunction angular.isFunction() }
* {@link angular.isNumber angular.isNumber() }
* {@link angular.isObject angular.isObject() }
* {@link angular.isString angular.isString() }
* {@link angular.isUndefined angular.isUndefined() }
## Strings
* {@link angular.lowercase angular.lowercase() }
* {@link angular.uppercase angular.uppercase() }
### JSON
* {@link angular.fromJson angular.fromJson() }
* {@link angular.toJson angular.toJson() }
## Utility methods for JavaScript types
* {@link angular.Object Object API} - Utility functions for JavaScript objects
* {@link angular.Array Array API} - Utility functions for JavaScript arrays
+107
View File
@@ -0,0 +1,107 @@
@ngdoc overview
@name Cookbook: Advanced Form
@description
Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
function UserForm() {
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
this.cancel();
}
UserForm.prototype = {
cancel: function() {
this.form = angular.copy(this.master);
},
save: function() {
this.master = this.form;
this.cancel();
}
};
</script>
<div ng:controller="UserForm">
<form name="myForm">
<label>Name:</label><br/>
<input type="text" ng:model="form.name" required/> <br/><br/>
<label>Address:</label> <br/>
<input type="text" ng:model="form.address.line1" size="33" required/> <br/>
<input type="text" ng:model="form.address.city" size="12" required/>,
<input type="text" ng:model="form.address.state" size="2"
ng:pattern="state" required/>
<input type="text" ng:model="form.address.zip" size="5"
ng:pattern="zip" required/><br/><br/>
<label>Contacts:</label>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng:model="contact.value" required/>
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button>
<button ng:click="save()" ng:disabled="{{myForm.$invalid || master.$equals(form)}}">Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}
master={{master}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
#Things to notice
* Cancel & save buttons are only enabled if the form is dirty — there is something to cancel or
save.
* Save button is only enabled if there are no validation errors on the form.
* Cancel reverts the form changes back to original state.
* Save updates the internal model of the form.
* Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Resources - Buzz
@description
@@ -6,54 +5,55 @@
External resources are URLs that provide JSON data, which are then rendered with the help of
templates. angular has a resource factory that can be used to give names to the URLs and then
attach behavior to them. For example you can use the
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz API}
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz
API}
to retrieve Buzz activity and comments.
<doc:example>
<doc:source>
<script>
BuzzController.$inject = ['$resource'];
function BuzzController($resource){
function BuzzController($resource) {
this.userId = 'googlebuzz';
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
{ get: {method:'JSON', params:{visibility:'@self'}},
replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}
{alt: 'json', callback: 'JSON_CALLBACK'},
{ get: {method: 'JSON', params: {visibility: '@self'}},
replies: {method: 'JSON', params: {visibility: '@self', comments: '@comments'}}
});
}
BuzzController.prototype = {
fetch: function(){
fetch: function() {
this.activities = this.Activity.get({userId:this.userId});
},
expandReplies: function(activity) {
activity.replies =
this.Activity.replies({userId:this.userId, activityId:activity.id});
activity.replies = this.Activity.replies({userId: this.userId, activityId: activity.id});
}
};
</script>
<div ng:controller="BuzzController">
<input name="userId" value="googlebuzz"/>
<input ng:model="userId"/>
<button ng:click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng:repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href="" ng:click="expandReplies(item)" style="float: right;">
<img ng:src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng:href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href ng:click="expandReplies(item)" style="float: right;">
Expand replies: {{item.links.replies[0].count}}
</a>
</h1>
{{item.object.content | html}}
<div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
<img ng:src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng:href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
{{reply.content | html}}
</div>
</div>
</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();
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Deep Linking
@description
@@ -28,24 +27,23 @@ 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 ./static/settings.html}
* {@link ./static/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>
<doc:source jsfiddle="false">
<script>
AppCntl.$inject = ['$route']
function AppCntl($route) {
// define routes
$route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
$route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
$route.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
$route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
$route.parent(this);
// initialize the model to something useful
@@ -57,28 +55,30 @@ The two partials are defined in the following URLs:
function WelcomeCntl($route){}
WelcomeCntl.prototype = {
greet: function(){
greet: function() {
alert("Hello " + this.person.name);
}
};
function SettingsCntl(){
SettingsCntl.$inject = ['$location'];
function SettingsCntl($location){
this.$location = $location;
this.cancel();
}
SettingsCntl.prototype = {
cancel: function(){
cancel: function() {
this.form = angular.copy(this.person);
},
save: function(){
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}}
@@ -88,7 +88,7 @@ The two partials are defined in the following URLs:
</div>
</doc:source>
<doc:scenario>
it('should navigate to URL', function(){
it('should navigate to URL', function() {
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
@@ -105,10 +105,12 @@ The two partials are defined in the following URLs:
# Things to notice
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
initialization of the {@link angular.service.$rouet $route} service with the proper URL routes.
* The {@link angular.service.$route $route} service then watches the URL and instantiates the
initialization of the {@link api/angular.service.$route $route} service with the proper URL
routes.
* The {@link api/angular.service.$route $route} service then watches the URL and instantiates the
appropriate controller when the URL changes.
* The {@link angular.widget.ng:view ng:view} widget loads the view when the URL changes. It also
* The {@link api/angular.widget.ng:view ng:view} widget loads the view when the URL changes. It
also
sets the view scope to the newly instantiated controller.
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
the URL is changed programatically or by the user.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Form
@description
@@ -11,7 +10,7 @@ allow a user to enter data.
<doc:example>
<doc:source>
<script>
function FormController(){
function FormController() {
this.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
@@ -24,24 +23,26 @@ allow a user to enter data.
<div ng:controller="FormController" class="example">
<label>Name:</label><br/>
<input type="text" name="user.name" ng:required/> <br/><br/>
<input type="text" ng:model="user.name" required/> <br/><br/>
<label>Address:</label><br/>
<input type="text" name="user.address.line1" size="33" ng:required/> <br/>
<input type="text" name="user.address.city" size="12" ng:required/>,
<input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="user.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
<input type="text" ng:model="user.address.line1" size="33" required> <br/>
<input type="text" ng:model="user.address.city" size="12" required>,
<input type="text" ng:model="user.address.state" size="2"
ng:pattern="state" required>
<input type="text" ng:model="user.address.zip" size="5"
ng:pattern="zip" required><br/><br/>
<label>Phone:</label>
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
<div ng:repeat="contact in user.contacts">
<select name="contact.type">
<select ng:model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
<input type="text" ng:model="contact.value" required/>
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
</div>
<hr/>
@@ -51,39 +52,37 @@ allow a user to enter data.
</doc:source>
<doc:scenario>
it('should show debug', function(){
it('should show debug', function() {
expect(binding('user')).toMatch(/John Smith/);
});
it('should add contact', function(){
it('should add contact', function() {
using('.example').element('a:contains(add)').click();
using('.example div:last').input('contact.value').enter('you@example.org');
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
expect(binding('user')).toMatch(/you@example.org/);
});
it('should remove contact', function(){
it('should remove contact', function() {
using('.example').element('a:contains(X)').click();
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
it('should validate zip', function(){
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.not().toMatch(/ng-validation-error/)
it('should validate zip', function() {
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
.toMatch(/ng-validation-error/)
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function(){
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
.not().toMatch(/ng-validation-error/)
it('should validate state', function() {
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
.toMatch(/ng-validation-error/)
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
@@ -91,12 +90,13 @@ allow a user to enter data.
# Things to notice
* The user data model is initialized {@link angular.ng:controller controller} and is available in
the {@link angular.scope scope} with the initial data.
* The user data model is initialized {@link api/angular.directive.ng:controller controller} and is
available in
the {@link api/angular.scope scope} with the initial data.
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link angular.widget.HTML input widgets} simply refer to the model and are auto bound.
* The inputs {@link angular.validator validate}. (Try leaving them blank or entering non digits
* The {@link api/angular.widget.input input widgets} simply refer to the model and are data-bound.
* The inputs {@link guide/dev_guide.forms validate}. (Try leaving them blank or entering non digits
in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
@@ -1,16 +1,22 @@
@workInProgress
@ngdoc overview
@name Cookbook: Hello World
@description
<doc:example>
<doc:source>
Your name: <input type="text" name="name" value="World"/>
<hr/>
Hello {{name}}!
<script>
function HelloCntl() {
this.name = 'World';
}
</script>
<div ng:controller="HelloCntl">
Your name: <input type="text" ng:model="name" value="World"/>
<hr/>
Hello {{name}}!
</div>
</doc:source>
<doc:scenario>
it('should change the binding when user enters text', function(){
it('should change the binding when user enters text', function() {
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
@@ -22,10 +28,11 @@
Take a look through the source and note:
* The script tag that {@link guide.bootstrap bootstraps} the angular environment.
* The text {@link angular.widget.HTML input widget} which is bound to the greeting name text.
* The script tag that {@link guide/dev_guide.bootstrap bootstraps} the angular environment.
* The text {@link api/angular.widget.input input widget} which is bound to the greeting name text.
* No need for listener registration and event firing on change events.
* The implicit presence of the `name` variable which is in the root {@link angular.scope scope}.
* The implicit presence of the `name` variable which is in the root {@link api/angular.scope scope}.
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
* The concept of {@link guide.data-binding data binding}, which reflects any changes to the
* The concept of {@link guide/dev_guide.templates.databinding data binding}, which reflects any
changes to the
input field in the greeting text.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook
@description
@@ -8,33 +7,33 @@ Welcome to the angular cookbook. Here we will show you typical uses of angular b
# Hello World
{@link cookbook.helloworld Hello World}: The simplest possible application that demonstrates the
{@link helloworld Hello World}: The simplest possible application that demonstrates the
classic Hello World!
# Basic Form
{@link cookbook.form Basic Form}: Displaying forms to the user for editing is the bread and butter
{@link form Basic Form}: Displaying forms to the user for editing is the bread and butter
of web applications. Angular makes forms easy through bidirectional data binding.
# Advanced Form
{@link cookbook.formadvanced Advanced Form}: Taking the form example to the next level and
{@link advancedform Advanced Form}: Taking the form example to the next level and
providing advanced features such as dirty detection, form reverting and submit disabling if
validation errors exist.
# Model View Controller
{@link cookbook.mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
{@link mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
separation aids in maintainability and testability of your project.
# Multi-page App and Deep Linking
{@link cookbook.deeplinking Deep Linking}: An AJAX application never navigates away from the
{@link deeplinking Deep Linking}: An AJAX application never navigates away from the
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
prevent you from emailing links to a particular screen within your application.
@@ -45,7 +44,7 @@ allowing you to send links to specific screens in your app.
# Services
{@link angular.service Services}: Services are long lived objects in your applications that are
{@link api/angular.service Services}: Services are long lived objects in your applications that are
available across controllers. A collection of useful services are pre-bundled with angular but you
will likely add your own. Services are initialized using dependency injection, which resolves the
order of initialization. This safeguards you from the perils of global state (a common way to
@@ -54,7 +53,6 @@ implement long lived objects).
# External Resources
{@link cookbook.buzz Resources}: Web applications must be able to communicate with the external
{@link buzz Resources}: Web applications must be able to communicate with the external
services to get and update data. Resources are the abstractions of external URLs which are
specially tailored to angular data binding.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: MVC
@description
@@ -13,9 +12,10 @@ no connection between the controller and the view.
<doc:example>
<doc:source>
<doc:source>
<script>
function TicTacToeCntl(){
function TicTacToeCntl($location){
this.$location = $location;
this.cellStyle= {
'height': '20px',
'width': '20px',
@@ -25,7 +25,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) {
@@ -35,7 +35,7 @@ no connection between the controller and the view.
this.setUrl();
}
},
reset: function(){
reset: function() {
this.board = [
['', '', ''],
['', '', ''],
@@ -45,67 +45,66 @@ no connection between the controller and the view.
this.winner = '';
this.setUrl();
},
grade: function(){
var b = this.board;
this.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(r) { return same(b[r][0], b[r][1], b[r][2]);}
function col(c) { return same(b[0][c], b[1][c], b[2][c]);}
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
grade: function() {
var b = this.board;
this.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
},
setUrl: function(){
setUrl: function() {
var rows = [];
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(value) {
readUrl: function(scope, value) {
if (value) {
value = value.split('/');
this.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, i){
this.board[i] = row.split(',');
angular.forEach(value[0].split(';'), function(row, col){
this.board[col] = row.split(',');
}, this);
this.grade();
} else {
this.reset();
}
}
};
</script>
<h3>Tic-Tac-Toe</h3>
<div ng:controller="TicTacToeCntl">
Next Player: {{nextMove}}
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
<table class="board">
<tr ng:repeat="row in board" style="height:15px;">
<td ng:repeat="cell in row" ng:style="cellStyle"
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng:click="reset()">reset board</button>
<table class="board">
<tr ng:repeat="row in board" style="height:15px;">
<td ng:repeat="cell in row" ng:style="cellStyle"
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng:click="reset()">reset board</button>
</div>
</doc:source>
<doc:scenario>
it('should play a game', function(){
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
expect(binding('nextMove')).toEqual('X');
piece(1, 2);
piece(3, 2);
piece(1, 3);
expect(element('.winner').text()).toEqual('Player X has won!');
});
</doc:source>
<doc:scenario>
it('should play a game', function() {
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
expect(binding('nextMove')).toEqual('X');
piece(1, 2);
piece(3, 2);
piece(1, 3);
expect(element('.winner').text()).toEqual('Player X has won!');
});
function piece(row, col) {
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
}
</doc:scenario>
function piece(row, col) {
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
}
</doc:scenario>
</doc:example>
@@ -114,12 +113,12 @@ no connection between the controller and the view.
* The controller is defined in JavaScript and has no reference to the rendering logic.
* The controller is instantiated by <angular/> and injected into the view.
* The controller can be instantiated in isolation (without a view) and the code will still execute.
This makes it very testable.
This makes it very testable.
* The HTML view is a projection of the model. In the above example, the model is stored in the
board variable.
board variable.
* All of the controller's properties (such as board and nextMove) are available to the view.
* Changing the model changes the view.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
hash so the browser's back button will undo game steps. See deep-linking. This example calls
{@link angular.Scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
api/angular.scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
@@ -0,0 +1,100 @@
@ngdoc overview
@name Developer Guide: Initializing Angular: Automatic Initialization
@description
Angular initializes automatically when you load the angular script into your page, specifying
angular's `ng:autobind` attribute with no arguments:
<script src="angular.js" ng:autobind>
From a high-level view, this is what happens during angular's automatic initialization process:
1. The browser loads the page, and then runs the angular script.
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
compilation phase is initiated in the page's `onLoad()` handler. Angular doesn't begin processing
the page until after the page load is complete.
2. Angular finds the root of the HTML document and creates the global variable `angular` in the
global namespace. Everything that angular subsequently creates is bound to fields in this global
object.
3. Angular walks the DOM looking for angular widgets, directives, and markup (such as `ng:init` or
`ng:repeat`). As angular encounters these, it creates child scopes as necessary and attaches them
to the DOM, registers listeners on those scopes, associates any controller functions with their
data and their part of the view, and ultimately constructs a runnable application. The resulting
app features two-way data-binding and a nice separation between data, presentation, and business
logic.
4. For the duration of the application session (while the page is loaded), angular monitors the
state of the application, and updates the view and the data model whenever the state of either one
changes.
For details on how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
## Initialization Options
The reason why `ng:autobind` exists is because angular should not assume that the entire HTML
document should be processed just because the `angular.js` script is included. In order to compile
only a part of the document, specify the ID of the element you want to use for angular's root
element as the value of the `ng:autobind` attribute:
ng:autobind="angularContent"
## Auto-bootstrap with `#autobind`
In some rare cases you can't define the `ng:` prefix before the script tag's attribute (for
example, in some CMS systems). In those situations it is possible to auto-bootstrap angular by
appending `#autobind` to the `<script src=...>` URL, like in this snippet:
<pre>
<!doctype html>
<html>
<head>
<script type="text/javascript"
src="http://code.angularjs.org/angular.js#autobind"></script>
</head>
<body>
<div xmlns:ng="http://angularjs.org">
Hello {{'world'}}!
</div>
</body>
</html>
</pre>
As with `ng:autobind`, you can specify an element id that should be exclusively targeted for
compilation as the value of the `#autobind`, for example: `#autobind=angularContent`.
## Filename Restrictions for Auto-bootstrap
In order for us to find the auto-bootstrap from a script attribute or URL fragment, the value of
the `script` `src` attribute that loads the angular script must match one of these naming
conventions:
- `angular.js`
- `angular-min.js`
- `angular-x.x.x.js`
- `angular-x.x.x.min.js`
- `angular-x.x.x-xxxxxxxx.js` (dev snapshot)
- `angular-x.x.x-xxxxxxxx.min.js` (dev snapshot)
- `angular-bootstrap.js` (used for development of angular)
Optionally, any of the filename formats above can be prepended with a relative or absolute URL that
ends with `/`.
## Global Angular Object
The angular script creates a single global variable `angular` in the global namespace. All angular
APIs are bound to fields of this global object.
## Related Topics
* {@link dev_guide.bootstrap Initializing Angular}
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
## Related API
{@link api/angular.compile Compiler API}
@@ -0,0 +1,46 @@
@ngdoc overview
@name Developer Guide: Initializing Angular: Manual Initialization
@description
Letting angular handle the initialization process (bootstrapping) is a handy way to start using
angular, but advanced users who want more control over the initialization process can choose to use
the manual bootstrapping method instead.
The best way to get started with manual bootstrapping is to look at the what happens when you use
{@link api/angular.directive.ng:autobind ng:autobind}, by showing each step of the process
explicitly.
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
<script src="http://code.angularjs.org/angular.js"></script>
<script>
angular.element(document).ready(function() {
angular.compile(document)();
});
</script>
</head>
<body>
Hello {{'World'}}!
</body>
</html>
</pre>
This is the sequence that your code should follow if you bootstrap angular on your own:
1. After the page is loaded, find the root of the HTML template, which is typically the root of
the document.
2. Run angular's {@link dev_guide.compiler Angular HTML compiler}, which converts a template into
an executable, bi-directionally bound application.
## Related Topics
* {@link dev_guide.bootstrap Initializing Angular}
* {@link dev_guide.bootstrap.auto_bootstrap Automatic Initialization}
* {@link dev_guide.compiler Angular HTML compiler}
## Related API
{@link api/angular.compile Compiler API}
@@ -0,0 +1,64 @@
@ngdoc overview
@name Developer Guide: Initializing Angular
@description
Initializing angular consists of loading the `angular.js` script in your page, and specifying how
angular should process and manage the page. To initialize angular you do the following:
* Specify the angular namespace in the `<html>` page
* Choose which flavor of angular script to load (debug or production)
* Specify whether or not angular should process and manage the page automatically (`ng:autobind`)
The simplest way to initialize angular is to load the angular script and tell angular to compile
and manage the whole page. You do this as follows:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
...
</head>
<body>
...
<script src="angular.js" ng:autobind>
</body>
</pre>
## Specifying the Angular Namespace
<html xmlns:ng="http://angularjs.org">
You need to declare the angular namespace declaration in the following cases:
* For all types of browser if you are using XHTML.
* For Internet Explorer older than version 9 (because older versions of IE do not render widgets
properly for either HTML or XHTML).
## Creating Your Own Namespaces
When you are ready to define your own {@link dev_guide.compiler.widgets widgets}, you must create
your own namespace in addition to specifying the angular namespace. You use your own namespace to
form the fully qualified name for widgets that you create.
For example, you could map the alias `my` to your domain, and create a widget called `my:widget`.
To create your own namespace, simply add another `xmlns` tag to your page, create an alias, and set
it to your unique domain:
<html xmlns:ng="http://angularjs.org" xmlns:my="http://mydomain.com">
## Loading the Angular Bootstrap Script
The angular bootstrap script comes in two flavors; a debug script, and a production script:
* angular-[version].js - This is a human-readable file, suitable for development and debugging.
* angular-[version].min.js - This is a compressed and obfuscated file, suitable for use in
production.
## Related Topics
* {@link dev_guide.bootstrap.auto_bootstrap Automatic Initialization}
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
@@ -0,0 +1,38 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Directives: Creating Custom Angular Directives
@description
The following code snippet shows how to define a custom directive. You define a new directive by
extending the {@link dev_guide.compiler Angular HTML compiler}. The code snippet below is a
simplified definition of the built-in {@link api/angular.directive.ng:bind ng:bind} directive:
<pre>
angular.directive('ng:bind', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
linkElement.text(value);
});
};
});
</pre>
# Additional Compiler Methods for Custom Directives
The angular compiler exposes methods that you may need to use when writing your own widgets and
directives. For example, the `descend()` method lets you control whether the compiler ignores or
processes child elements of the element it is compiling. For information on this and other
compiler methods, see the {@link api/angular.compile Compiler API doc}.
## Related Docs
* {@link dev_guide.compiler.directives Understanding Angular Directives}
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
* {@link dev_guide.compiler Angular HTML Compiler}
## Related API
* {@link api/angular.directive Angular Directive API}.
@@ -0,0 +1,46 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Directives
@description
An angular directive is a custom HTML attribute that angular knows how to process. You add them to
a template element like any other attribute. Angular directives all have a `ng:` prefix. In the
following example, the angular directive (`ng:controller`) is a div tag:
<div ng:controller>
You use angular directives to modify DOM element properties. The element you modify can be an
existing HTML element type or a custom DOM element type that you created. You can use any number of
directives per element.
You add angular directives to a standard HTML tag as in the following example, in which we have
added the {@link api/angular.directive.ng:click ng:click} directive to a button tag:
<button ng:model="button1" ng:click="foo()">Click This</button>
In the example above, `name` is the standard HTML attribute, and `ng:click` is the angular
directive. The `ng:click` directive lets you implement custom behavior in an associated controller
function.
In the next example, we add the {@link api/angular.directive.ng:bind ng:bind} directive to a
`<span>` tag:
<span ng:bind="1+2"></span>
The `ng:bind` directive tells angular to set up {@link dev_guide.templates.databinding data
binding} between the data model and the view for the specified expression. When the angular {@link
dev_guide.compiler compiler} encounters an `ng:bind` directive in a template, it passes the
attribute value to the `ng:bind` function, which in turn sets up the data binding. On any change to
the expression in the model, the view is updated to display the span text with the changed
expression value.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.directives.creating_directives Creating Angular Directives}
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
## Related API:
* {@link api/angular.directive Directive API}
* {@link api/angular.widget Widget API}
@@ -0,0 +1,47 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Comparing Directives and Attribute Widgets
@description
Although directives and {@link dev_guide.compiler.widgets attribute widgets} appear the same in a
template (`ng:init` is a directive, `ng:repeat` is an attribute widget), there is a difference in
the order in which they are evaluated. The user of existing directives or widgets cannot determine
the order of evaluation. The evaluation order is the responsibility of the developer creating
custom directives and widgets.
For example, consider this piece of HTML, which uses the `ng:repeat`, `ng:init`, and `ng:bind`
widget and directives:
<pre>
<ul ng:init="people=['mike', 'mary']">
<li ng:repeat="person in people"
ng:init="a=a+1"
ng:bind="person">
</li>
</ul>
</pre>
Notice that the order of execution matters here. Because we want to run the `ng:init="a=a+1` and
`ng:bind="person"` once for each `person in people`, we need to execute {@link
api/angular.widget.@ng:repeat ng:repeat} to make copies of the `<li>` element before we run the
{@link api/angular.directive.ng:init ng:init}, and {@link api/angular.directive.ng:bind ng:bind}
for each of the `<li>`copies.
If you implemented `ng:repeat` as a directive, there would be no guarantee that the attributes
`ng:repeat`, `ng:init`, and `ng:bind` would be evaluated in the order they are declared, because
the order of element attributes in HTML is not significant to the browser.
So, when creating a custom HTML attribute, you will have to consider whether a directive or a
widget is more appropriate. When the order of execution doesn't matter, directives are the right
choice. In a situation where the order matters and one attribute should be processed with a higher
priority than others, use a widget for the attribute that must be processed first.
## Related Topics
* {@link dev_guide.compiler.directives Understanding Angular Directives}
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
## Related API:
* {@link api/angular.directive Directive API}
* {@link api/angular.widget Widget API}
@@ -0,0 +1,96 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Extending the Angular Compiler
@description
Let's say that we want to create a new DOM element called `<my:greeter/>` that displays a greeting.
We want this HTML source:
<pre>
<div ng:init="s='Hello'; n='World'">
<my:greeter salutation="s" name="n"></my:greeter>
</div>
</pre>
to produce this DOM:
<pre>
<div ng:init="s='Hello'; n='World'">
<my:greeter salutation="s" name="n"/>
<span class="salutation">Hello</span>
<span class="name">World</span>!
</my:greeter>
</div>
</pre>
That is, the new `<my:greeter></my:greeter>` tag's `salutation` and `name` attributes should be
transformed by the compiler such that two `<span>` tags display the values of the attributes, with
CSS classes applied to the output.
The following code snippet shows how to write a following widget definition that will be processed
by the compiler. Note that you have to declare the {@link dev_guide.bootstrap namespace} `my` in
the page:
<pre>
angular.widget('my:greeter', function(compileElement){
var compiler = this;
compileElement.css('display', 'block');
var salutationExp = compileElement.attr('salutation');
var nameExp = compileElement.attr('name');
return function(linkElement){
var salutationSpan = angular.element('<span class="salutation"></span');
var nameSpan = angular.element('<span class="name"></span>');
linkElement.append(salutationSpan);
linkElement.append(' ');
linkElement.append(nameSpan);
linkElement.append('!');
this.$watch(salutationExp, function(value){
salutationSpan.text(value);
});
this.$watch(nameExp, function(value){
nameSpan.text(value);
});
};
});
</pre>
Note: For more about widgets, see {@link dev_guide.compiler.widgets Understanding Angular Widgets}
and the {@link api/angular.widget widget API reference page}.
# Compilation process for `<my:greeter>`
Here are the steps that the compiler takes in processing the page that contains the widget
definition above:
## Compile Phase
1. Recursively traverse the DOM depth-first.
2. Find the angular.widget definition.
3. Find and execute the widget's compileElement function, which includes the following steps:
1. Add a style element with attribute display: block; to the template DOM so that the browser
knows to treat the element as block element for rendering. (Note: because this style element was
added on the template compileElement, this style is automatically applied to any clones of the
template (i.e. any repeating elements)).
2. Extract the salutation and name HTML attributes as angular expressions.
4. Return the aggregate link function, which includes just one link function in this example.
## Link Phase
1. Execute the aggregate link function, which includes the following steps:
1. Create a <span> element set to the salutation class
2. Create a <span> element set to the name class.
2. Add the span elements to the linkElement. (Note: be careful not to add them to the
compileElement, because that's the template.)
3. Set up watches on the expressions. When an expression changes, copy the data to the
corresponding spans.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
## Related API
* {@link api/angular.compile angular.compile()}
@@ -0,0 +1,92 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Markup
@description
Markup in angular is a feature that you can use in templates to transform the content of DOM
elements prior to the compile phase (in which elements are compiled and link functions are
returned. See the {@link dev_guide.compiler compiler docs} for details on how the compiler
works.) The ability to make pre-compile changes to DOM elements lets you create shorthand for
{@link api/angular.widget widget} and {@link api/angular.directive directive} declarations.
Angular provides one built-in markup feature: the double curly-braces used to declare binding
points (between the model and view) for angular expressions. You can also create your own custom
markup.
# Using Double Curly-brace Markup (`{{ }}`)
The double curly-brace (`{{ }}`) markup translates an enclosed expression into an {@link
api/angular.directive.ng:bind ng:bind} directive:
<pre>
{{expression}}
</pre>
is transformed to:
<pre>
<span ng:bind="expression"></span>
</pre>
Markup is useful for the simple reason that `{{1+2}}` is easier to write and understand than `<span
ng:bind="1+2"></span>`. After markup shorthand is expanded into the DOM elements it represents, the
expanded elements are then {@link dev_guide.compiler compiled} normally.
# Creating Custom Markup
Let's say you want to define markup that transforms `---` into a horizontal rule (`<hr/>`):
<pre>
header
---
footer
</pre>
should translate to:
<pre>
header
<hr/>
footer
</pre>
Here is how you could extend the angular compiler to create the "---" markup:
<pre>
angular.markup('---', function(text, textNode, parentElement) {
var compiler = this;
var index = text.indexOf('---');
if (index > -1) {
textNode.after(text.substring(index + 3));
textNode.after(angular.element('<hr>'));
textNode.after(text.substring(0, index));
textNode.remove();
}
});
</pre>
Unlike the way the compiler processes {@link api/angular.widget widgets} and {@link
api/angular.directive directives} (matching the name of the handler function to a DOM element or
attribute name), the compiler calls every markup handler for every text node, giving the handler a
chance to transform the text. The markup handler needs to find all the matches in the text.
## Attribute Markup
Attribute markup extends the angular compiler in a very similar way to markup, except that it
allows you to modify the state of attribute text rather then the content of a node.
<pre>
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
if (attrName == 'additional-class') {
element.addClass(attrValue);
}
});
</pre>
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
## Related API
* {@link api/angular.compile Compiler API Reference}
@@ -0,0 +1,26 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler
@description
The core of angular is its HTML compiler. The compiler processes angular directives, widgets, and
markup to transform a static HTML page into a dynamic web application.
The default HTML transformations that the angular compiler provides are useful for building generic
apps, but you can also extend the compiler to create a domain-specific language for building
specific types of web applications.
All compilation takes place in the web browser; no server is involved.
## Related Topics
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
* {@link dev_guide.compiler.directives Understanding Angular Directives}
* {@link dev_guide.compiler.markup Understanding Angular Markup}
## Related API
* {@link api/angular.compile Angular Compiler API}
@@ -0,0 +1,17 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Testing a New DOM Element
@description
"Testing, testing, come in, over?"
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.understanding_compiler Understanding How the Compiler Works}
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
## Related API
* {@link api/angular.compile angular.compile()}
@@ -0,0 +1,68 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding How the Compiler Works
@description
Every {@link api/angular.widget widget}, {@link api/angular.directive directive} and {@link
dev_guide.compiler.markup markup} is defined with a compile function, which the angular compiler
executes on each widget or directive it encounters. The compile function optionally returns a link
function. This compilation process happens automatically when the page is loaded when you specify
`ng:autobind` in the script tag from which you load the angular script file. (See {@link
dev_guide.bootstrap Initializing Angular}.)
The compile and link functions are related as follows:
* **compile function** — Registers a listener for the widget, directive, or markup expression. The
compiler calls this function exactly once.
* **link function** — Sets up the listener registered by the compile function. This function can be
called multiple times, once per cloned DOM element. For example, in the case of the {@link
api/angular.widget.@ng:repeat repeater widget} used in a list element (`<li ng:repeat="[item in
dataset]"`), the link function gets called to set up a listener on each element in the list.
Note that angular's built-in widgets, directives, and markup have predefined compile and link
functions that you don't need to modify. When you create your own widgets, directives, or markup,
you must write compile and link functions for them. Refer to the {@link api/angular.compile
Compiler API} for details.
When the angular compiler compiles a page, it proceeds through 3 phases: Compile, Create Root
Scope, and Link:
1. Compile Phase
1. Recursively traverse the DOM, depth-first.
2. Look for a matching compile function of type widget, then markup, then directive.
3. If a compile function is found then execute it.
4. When the compile function completes, it should return a link function. Aggregate this link
function with all link functions returned previously by step 3.
5. Repeat steps 3 and 4 for all compile functions found.
The result of the compilation phase is an aggregate link function, which comprises all of the
individual link functions.
2. Create Root Scope Phase
* Inject all services into the root scope.
3. Link Phase
1. Execute the aggregate link function with the root scope. The aggregate link function calls
all of the individual link functions that were generated in the compile phase.
2. If there are any clones of the DOM caused by repeating elements, call the link function
multiple times, one for each repeating item.
Note that while the compile function is executed exactly once, the link function can be executed
multiple times, for example, once for each iteration in a repeater.
The angular compiler exposes methods that you will need to make use of when writing your own
widgets and directives. For information on these methods, see the {@link api/angular.compile
Compiler API doc}.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.extending_compiler Extending the Angular Compiler}
* {@link dev_guide.compiler.testing_dom_element Testing a New DOM Element}
## Related API
* {@link api/angular.compile angular.compile()}
@@ -0,0 +1,95 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Widgets: Creating Custom Widgets
@description
When you create your own widgets, you must set up your own namespace for them. (See
dev_guide.bootstrap Initializing Angular} for information about namespaces in angular.)
Let's say we would like to create a new element type in the namespace `my` that can watch an
expression and `alert()` the user with each new value:
<pre>
// An element widget
<my:watch exp="name"></my:watch>
</pre>
You can implement `my:watch` like this:
<pre>
angular.widget('my:watch', function(compileElement) {
var compiler = this;
var exp = compileElement.attr('exp');
return function(linkElement) {
var currentScope = this;
currentScope.$watch(exp, function(value){
alert(value);
});
};
});
</pre>
# Creating a Custom Attribute Widget
Let's implement the same widget as in the example in Defining an Element Widget, but this time as
an attribute that can be added to any existing DOM element:
<pre>
// An attribute widget (my:watch) in a div tag
<div my:watch="name">text</div>
</pre>
You can implement `my:watch` attribute like this:
<pre>
angular.widget('@my:watch', function(expression, compileElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
alert(value);
});
};
});
</pre>
# Live Example of a Custom Element Widget
<doc:example>
<doc:source>
<script>
angular.widget('my:time', function(compileElement){
compileElement.css('display', 'block');
return function(linkElement){
function update() {
linkElement.text('Current time is: ' + new Date());
setTimeout(update, 1000);
}
update();
};
});
</script>
<my:time></my:time>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
# Additional Compiler Methods for Custom Widgets
The angular compiler exposes methods that you may need to use of when writing your own widgets and
directives. For example, the `descend()` method lets you control whether the compiler ignores or
processes child elements of the element it is compiling. For information on this and other
compiler methods, see the {@link api/angular.compile Compiler API doc}.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.directives Angular Directives}
* {@link dev_guide.compiler.widgets Angular Widgets}
* {@link dev_guide.compiler.directives.creating_directives Creating Custom Directives}
## Related API
* {@link api/angular.compile Compiler API}
@@ -0,0 +1,35 @@
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Widgets
@description
Widgets are DOM elements that the browser doesn't already understand. Angular provides some
built-in widgets (such as {@link api/angular.widget.@ng:repeat ng:repeat}), and you can create your
own custom widgets.
Widgets are intended to manipulate the DOM tree by adding new elements (unlike {@link
dev_guide.compiler.directives angular directives}, which are intended to modify only element
properties).
Widgets come in two types:
* Element Widget — A custom DOM element. An example of a custom element is shown in {@link
dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}.
* Attribute Widget — A custom attribute on an existing DOM element. An attribute widget is similar
to an angular directive, with the main difference being that an attribute widget will always be
processed before any directives that are specified on the same element. Only one attribute widget
is allowed per element. An example of an attribute widget is shown in {@link
dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.directives Angular Directives}
* {@link dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}
* {@link dev_guide.compiler.directives.creating_directives Creating Custom Directives}
## Related API
* {@link api/angular.compile Compiler API}
+32
View File
@@ -0,0 +1,32 @@
@ngdoc overview
@name Developer Guide: About Dependency Injection (DI)
@description
Dependency Injection (DI) is an object-oriented software design pattern that supports the
decoupling and dependency management of application components.
The idea behind DI is to decouple each component from all of the other components that it depends
on to do its particular job. The way this is done in DI is by moving the responsibility for
managing dependencies out of each individual component and into a provider component. The provider
(or injector) component manages the life cycles and dependencies for all of the other components in
an application.
Angular has a built-in dependency management subsystem that helps to make your applications easier
to develop, understand, and test.
For more information on DI in general, see {@link http://en.wikipedia.org/wiki/Dependency_injection
Dependency Injection} at Wikipedia, and {@link http://martinfowler.com/articles/injection.html
Inversion of Control} by Martin Fowler, or read about DI in your favorite software design pattern
book.
## Related Topics
* {@link dev_guide.di.understanding_di Understanding DI in Angular}
* {@link dev_guide.services Angular Services}
## Related API
* {@link api/angular.service Service API}
* {@link api/angular.injector Angular Injector API}
@@ -0,0 +1,105 @@
@ngdoc overview
@name Developer Guide: DI: Understanding DI in Angular
@description
While DI is widely used in statically typed languages such as Java or C++, it has not been widely
used in JavaScript. Angular brings the benefits of DI into JavaScript apps.
In angular, DI is implemented as a subsystem that manages dependencies between services,
controllers, widgets, and filters. The most important of these are {@link api/angular.service
services}.
Services are objects that handle common tasks in web applications. Angular provides several{@link
api/angular.service built-in services}, and you can create your own custom services.
The main job of angular's DI subsystem is to provide services to angular components that depend on
them. The way the DI subsystem provides services is as follows: all services are registered with
angular's {@link api/angular.service service API}, and all components that depend on services
define those dependencies as a property (`$inject`). With this information, the DI subsystem
manages the creation of service objects and the provision of those objects to the components that
need them, at the time they need them. The following illustration steps through the sequence of
events:
<img src="img/guide/di_sequence_final.png">
In the illustration above, the dependency injection sequence proceeds as follows:
1. Service factory functions are registered with angular's service factory repository.
2. `ng:autobind` triggers angular's bootstrap sequence, during which angular compiles the template,
creates the root scope, and creates the dependency injector.
3. The `ng:controller` directive implicitly creates a new child scope, augmented by the application
of the `PhoneListCtrl` controller function.
4. The Injector identifies the `$xhr` service as `PhoneListCtrl` controller's only dependency.
5. The Injector checks if the `$xhr` service has already been instantiated, and if not uses the
factory function from the service factory repository to construct it.
6. DI provides the instance of $xhr service to the PhoneListCtrl controller constructor
## How Scope Relates to DI
The {@link api/angular.injector injector} is responsible for resolving the service dependencies in
the application. It gets created and configured with the creation of a root scope. The injector
caches instances of services, with the services cache bound to the root scope.
Different root scopes have different instances of the injector. While typical angular applications
will only have one root scope (and hence the services will act like application singletons), in
tests it is important to not share singletons across test invocations for isolation reasons. We
achieve the necessary isolation by having each test create its own separate root scope.
<pre>
// create a root scope
var rootScope = angular.scope();
// access the service locator
var myService = rootScope.$service('myService');
</pre>
## Inferring dependencies from the signature of the factory function or constructor
**EXPERIMENTAL FEATURE**: This is an experimental feature. See the important note at the end of
this section for drawbacks.
We resort to `$inject` and our own annotation because there is no way in JavaScript to get a list
of arguments. Or is there? It turns out that calling `.toString()` on a function returns the
function declaration along with the argument names as shown below:
<pre>
function myFn(a,b){}
expect(myFn.toString()).toEqual('function myFn(a,b){}');
</pre>
This means that angular can infer the function names after all and use that information to generate
the `$inject` annotation automatically. Therefore the following two function definitions are
equivalent:
<pre>
// given a user defined service
angular.service('serviceA', ...);
// inject '$window', 'serviceA', curry 'name';
function fnA($window, serviceA, name){};
fnA.$inject = ['$window', 'serviceA'];
// inject '$window', 'serviceA', curry 'name';
function fnB($window, serviceA_, name){};
// implies: fnB.$inject = ['$window', 'serviceA'];
</pre>
If angular does not find a `$inject` annotation on the function, then it calls the `.toString()`
method and tries to infer what should be injected by using function argument names as dependency
identifiers.
**IMPORTANT**
Minifiers/obfuscators change the names of function arguments and will therefore break the `$inject`
inference. For this reason, either explicitly declare the `$inject` or do not use
minifiers/obfuscators. In the future, we may provide a pre-processor which will scan the source
code and insert the `$inject` into the source code so that it can be minified/obfuscated.
## Related Topics
* {@link dev_guide.services Angular Services}
## Related API
* {@link api/angular.service Services API}
@@ -0,0 +1,53 @@
@ngdoc overview
@name Developer Guide: DI: Using DI in Controllers
@description
The most common place to use dependency injection in angular applications is in {@link
dev_guide.mvc.understanding_controller controllers}. Here is a simple example:
<pre>
function MyController($route){
// configure the route service
$route.when(...);
}
MyController.$inject = ['$route'];
</pre>
In this example, the `MyController` constructor function takes one argument, the {@link
api/angular.service.$route $route} service. Angular is then responsible for supplying the instance
of `$route` to the controller when the constructor is instantiated. There are two ways to cause
controller instantiation by configuring routes with the `$route` service, or by referencing the
controller from the HTML template, as follows:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
<script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
<body>
...
</body>
</html>
</pre>
When angular is instantiating your controller, it needs to know what services, if any, should be
injected (passed in as arguments) into the controller. Since there is no reflection in JavaScript,
we have to supply this information to angular in the form of an additional property on the
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
<pre>
MyController.$inject = ['$route'];
</pre>
The information in `$inject` is then used by the {@link api/angular.injector injector} to call the
function with the correct arguments.
## Related Topics
* {@link dev_guide.di About Dependency Injection}
* {@link dev_guide.di.understanding_di Understanding Dependency Injection in Angular}
* {@link dev_guide.services Angular Services}
## Related API
* {@link api/angular.injector Angular Injector API}
@@ -0,0 +1,235 @@
@ngdoc overview
@name Developer Guide: Understanding Angular Expressions
@description
Expressions are {@link dev_guide.templates.databinding bindings} that you write in HTML and embed
in templates in order to create views in angular. Angular expressions are similar but not
equivalent to JavaScript expressions.
For example, these are all valid expressions in angular:
* `1+2={{1+2}}`
* `3*10|currency`
* `Hello {{name}}!`
* `Hello {{'World'}}!`
## Angular Expressions vs. JS Expressions
It might be tempting to think of angular view expressions as JavaScript expressions, but that is
not entirely correct. Angular does not use a simple JavaScript eval of the expression text. You can
think of angular expressions as JavaScript expressions with these differences:
* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to the
global window as in JavaScript.
* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript.
* **No Control Flow Statements:** you cannot do the following from an angular expression:
conditionals, loops, or throw.
* **Type Augmentation:** the scope expression evaluator augments built-in types.
* **Filters:** you can add filters to an expression, for example to convert raw data into a
human-readable format.
* **The $:** angular reserves this prefix to differentiate its API names from others.
If, on the other hand, you do want to run arbitrary JavaScript code, you should make it a
controller method and call that. If you want to `eval()` an angular expression from JavaScript, use
the `Scope:$eval()` method.
## Example
<doc:example>
<doc:source>
1+2={{1+2}}
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function() {
expect(binding('1+2')).toEqual('3');
});
</doc:scenario>
</doc:example>
You can try evaluating different expressions here:
<doc:example>
<doc:source>
<script>
function Cntl2() {
this.exprs = [];
this.expr = '3*10|currency';
}
</script>
<div ng:controller="Cntl2" class="expressions">
Expression:
<input type='text' ng:model="expr" size="80"/>
<button ng:click="exprs.$add(expr)">Evaluate</button>
<ul>
<li ng:repeat="expr in exprs">
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
</li>
</ul>
</div>
</doc:source>
<doc:scenario>
it('should allow user expression testing', function() {
element('.expressions :button').click();
var li = using('.expressions ul').repeater('li');
expect(li.count()).toBe(1);
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
});
</doc:scenario>
</doc:example>
# Attribute Evaluation
Evaluation of all attributes takes place against the current scope. Unlike JavaScript, where names
default to global window properties, angular expressions have to use `$window` to refer to the
global object. For example, if you want to call `alert()`, which is defined on `window`, an
expression must use `$window.alert()`. This is done intentionally to prevent accidental access to
the global state (a common source of subtle bugs).
<doc:example>
<doc:source>
<script>
function Cntl1($window){
this.name = 'World';
this.greet = function() {
($window.mockWindow || $window).alert('Hello ' + this.name);
}
}
</script>
<div class="example2" ng:controller="Cntl1">
Name: <input ng:model="name" type="text"/>
<button ng:click="greet()">Greet</button>
</div>
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function() {
var alertText;
this.addFutureAction('set mock', function($window, $document, done) {
$window.mockWindow = {
alert: function(text){ alertText = text; }
};
done();
});
element(':button:contains(Greet)').click();
expect(this.addFuture('alert text', function(done) {
done(null, alertText);
})).toBe('Hello World');
});
</doc:scenario>
</doc:example>
## Forgiving
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating `a.b.c` throws
an exception if `a` is not an object. While this makes sense for a general purpose language, the
expression evaluations are primarily used for data binding, which often look like this:
{{a.b.c}}
It makes more sense to show nothing than to throw an exception if `a` is undefined (perhaps we are
waiting for the server response, and it will become defined soon). If expression evaluation wasn't
forgiving we'd have to write bindings that clutter the code, for example: `{{((a||{}).b||{}).c}}`
Similarly, invoking a function `a.b.c()` on undefined or null simply returns undefined.
Assignments work the same way in reverse:
a.b.c = 10
...creates the intermediary objects even if a is undefined.
## No Control Flow Statements
You cannot write a control flow statement in an expression. The reason behind this is core to the
angular philosophy that application logic should be in controllers, not in the view. If you need a
conditional (including ternary operators), loop, or to throw from a view expression, delegate to a
JavaScript method instead.
## Type Augmentation
Built-in types have methods like `[].push()`, but the richness of these methods is limited.
Consider the example below, which allows you to do a simple search over a canned set of contacts.
The example would be much more complicated if we did not have the `Array:$filter()`. There is no
built-in method on `Array` called {@link api/angular.Array.filter $filter} and angular doesn't add
it to `Array.prototype` because that could collide with other JavaScript frameworks.
For this reason the scope expression evaluator augments the built-in types to make them act like
they have extra methods. The actual method for `$filter()` is `angular.Array.filter()`. You can
call it from JavaScript.
Extensions: You can further extend the expression vocabulary by adding new methods to
`angular.Array` or `angular.String`, etc.
<doc:example>
<doc:source>
<div ng:init="friends = [
{name:'John', phone:'555-1212'},
{name:'Mary', phone:'555-9876'},
{name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
Search: <input ng:model="searchText"/>
<table class="example3">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng:repeat="friend in friends.$filter(searchText)">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should filter the list', function() {
var tr = using('table.example3').repeater('tr.ng-attr-widget');
expect(tr.count()).toBe(5);
input('searchText').enter('a');
expect(tr.count()).toBe(2);
});
</doc:scenario>
</doc:example>
## Filters
When presenting data to the user, you might need to convert the data from its raw format to a
user-friendly format. For example, you might have a data object that needs to be formatted
according to the locale before displaying it to the user. You can pass expressions through a chain
of filters like this:
name | uppercase
The expression evaluator simply passes the value of name to angular.filter.uppercase.
Chain filters using this syntax:
value | filter1 | filter2
You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
2 decimal points:
123 | number:2
# The $
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
angular uses, to differentiate its API names from others. If angular didn't use $, then evaluating
`a.length()` would return undefined because neither a nor angular define such a property.
Consider that in a future version of angular we might choose to add a length method, in which case
the behavior of the expression would change. Worse yet, you the developer could create a length
property and then we would have a collision. This problem exists because angular augments existing
objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
so that angular developers and developers who use angular can develop in harmony without collisions.
## Related Topics
* {@link dev_guide.compiler.markup Understanding Angular Markup}
* {@link dev_guide.templates.filters Understanding Angular Filters}
## Related API
* {@link api/angular.compile Angular Compiler API}
+610
View File
@@ -0,0 +1,610 @@
@ngdoc overview
@name Developer Guide: Forms
@description
# Overview
Forms allow users to enter data into your application. Forms represent the bidirectional data
bindings in Angular.
Forms consist of all of the following:
- the individual widgets with which users interact
- the validation rules for widgets
- the form, a collection of widgets that contains aggregated validation information
# Form
A form groups a set of widgets together into a single logical data-set. A form is created using
the {@link api/angular.widget.form &lt;form&gt;} element that calls the
{@link api/angular.service.$formFactory $formFactory} service. The form is responsible for managing
the widgets and for tracking validation information.
A form is:
- The collection which contains widgets or other forms.
- Responsible for marshaling data from the model into a widget. This is
triggered by {@link api/angular.scope.$watch $watch} of the model expression.
- Responsible for marshaling data from the widget into the model. This is
triggered by the widget emitting the `$viewChange` event.
- Responsible for updating the validation state of the widget, when the widget emits
`$valid` / `$invalid` event. The validation state is useful for controlling the validation
errors shown to the user in it consist of:
- `$valid` / `$invalid`: Complementary set of booleans which show if a widget is valid / invalid.
- `$error`: an object which has a property for each validation key emited by the widget.
The value of the key is always true. If widget is valid, then the `$error`
object has no properties. For example if the widget emits
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
updated to `$error.REQUIRED == true`.
- Responsible for aggregating widget validation information into the form.
- `$valid` / `$invalid`: Complementary set of booleans which show if all the child widgets
(or forms) are valid or if any are invalid.
- `$error`: an object which has a property for each validation key emited by the
child widget. The value of the key is an array of widgets which fired the invalid
event. If all child widgets are valid then, then the `$error` object has no
properties. For example if a child widget emits
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
updated to `$error.REQUIRED == [ widgetWhichEmitedInvalid ]`.
# Widgets
In Angular, a widget is the term used for the UI with which the user input. Examples of
bult-in Angular widgets are {@link api/angular.widget.input input} and
{@link api/angular.widget.select select}. Widgets provide the rendering and the user
interaction logic. Widgets should be declared inside a form, if no form is provided an implicit
form {@link api/angular.service.$formFactory $formFactory.rootForm} form is used.
Widgets are implemented as Angular controllers. A widget controller:
- implements methods:
- `$render` - Updates the DOM from the internal state as represented by `$viewValue`.
- `$parseView` - Translate `$viewValue` to `$modelValue`. (`$modelValue` will be assigned to
the model scope by the form)
- `$parseModel` - Translate `$modelValue` to `$viewValue`. (`$viewValue` will be assigned to
the DOM inside the `$render` method)
- responds to events:
- `$validate` - Emitted by the form when the form determines that the widget needs to validate
itself. There may be more then one listener on the `$validate` event. The widget responds
by emitting `$valid` / `$invalid` event of its own.
- emits events:
- `$viewChange` - Emitted when the user interacts with the widget and it is necessary to update
the model.
- `$valid` - Emitted when the widget determines that it is valid (usually as a response to
`$validate` event or inside `$parseView()` or `$parseModel()` method).
- `$invalid` - Emitted when the widget determines that it is invalid (usually as a response to
`$validate` event or inside `$parseView()` or `$parseModel()` method).
- `$destroy` - Emitted when the widget element is removed from the DOM.
# CSS
Angular-defined widgets and forms set `ng-valid` and `ng-invalid` classes on themselves to allow
the web-designer a way to style them. If you write your own widgets, then their `$render()`
methods must set the appropriate CSS classes to allow styling.
(See {@link dev_guide.templates.css-styling CSS})
# Example
The following example demonstrates:
- How an error is displayed when a required field is empty.
- Error highlighting.
- How form submission is disabled when the form is invalid.
- The internal state of the widget and form in the the 'Debug View' area.
<doc:example>
<doc:source>
<style>
.ng-invalid { border: solid 1px red; }
.ng-form {display: block;}
</style>
<script>
function UserFormCntl() {
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
customer: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
}
};
this.cancel();
}
UserFormCntl.prototype = {
cancel: function() {
this.form = angular.copy(this.master);
},
save: function() {
this.master = this.form;
this.cancel();
}
};
</script>
<div ng:controller="UserFormCntl">
<form name="userForm">
<label>Name:</label><br/>
<input type="text" name="customer" ng:model="form.customer" required/>
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
Customer name is required!</span>
<br/><br/>
<ng:form name="addressForm">
<label>Address:</label> <br/>
<input type="text" name="line1" size="33" required
ng:model="form.address.line1"/> <br/>
<input type="text" name="city" size="12" required
ng:model="form.address.city"/>,
<input type="text" name="state" ng:pattern="state" size="2" required
ng:model="form.address.state"/>
<input type="text" name="zip" ng:pattern="zip" size="5" required
ng:model="form.address.zip"/><br/><br/>
<span class="error" ng:show="addressForm.$invalid">
Incomplete address:
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
Missing state!</span>
<span class="error" ng:show="addressForm.state.$error.PATTERN">
Invalid state!</span>
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
Missing zip!</span>
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
Invalid zip!</span>
</span>
</ng:form>
<button ng:click="cancel()"
ng:disabled="{{master.$equals(form)}}">Cancel</button>
<button ng:click="save()"
ng:disabled="{{userForm.$invalid || master.$equals(form)}}">
Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}</pre>
<pre>master={{master}}</pre>
<pre>userForm={{userForm}}</pre>
<pre>addressForm={{addressForm}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.customer').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.customer').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.customer').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[ng\\:model="form.customer"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
# Life-cycle
- The `<form>` element triggers creation of a new form {@link dev_guide.scopes scope} using the
{@link api/angular.service.$formFactory $formfactory}. The new form scope is added to the
`<form>` element using the jQuery `.data()` method for later retrieval under the key `$form`.
The form also sets up these listeners:
- `$destroy` - This event is emitted by nested widget when it is removed from the view. It gives
the form a chance to clean up any validation references to the destroyed widget.
- `$valid` / `$invalid` - This event is emitted by the widget on validation state change.
- `<input>` element triggers the creation of the widget using the
{@link api/angular.service.$formFactory $formfactory.$createWidget()} method. The `$createWidget()`
creates new widget instance by calling the current scope {@link api/angular.scope.$new .$new()} and
registers these listeners:
- `$watch` on the model scope.
- `$viewChange` event on the widget scope.
- `$validate` event on the widget scope.
- Element `change` event when the user enters data.
<img class="center" src="img/form_data_flow.png" border="1" />
- When the user interacts with the widget:
1. The DOM element fires the `change` event which the widget intercepts. Widget then emits
a `$viewChange` event which includes the new user-entered value. (Remember that the DOM events
are outside of the Angular environment so the widget must emit its event within the
{@link api/angular.scope.$apply $apply} method).
2. The form's `$viewChange` listener copies the user-entered value to the widget's `$viewValue`
property. Since the `$viewValue` is the raw value as entered by user, it may need to be
translated to a different format/type (for example, translating a string to a number).
If you need your widget to translate between the internal `$viewValue` and the external
`$modelValue` state, you must declare a `$parseView()` method. The `$parseView()` method
will copy `$viewValue` to `$modelValue` and perform any necessary translations.
3. The `$modelValue` is written into the application model.
4. The form then emits a `$validate` event, giving the widget's validators chance to validate the
input. There can be any number of validators registered. Each validator may in turn
emit a `$valid` / `$invalid` event with the validator's validation key. For example `REQUIRED`.
5. Form listens to `$valid`/`$invalid` events and updates both the form as well as the widget
scope with the validation state. The validation updates the `$valid` and `$invalid`, property
as well as `$error` object. The widget's `$error` object is updated with the validation key
such that `$error.REQUIRED == true` when the validation emits `$invalid` with `REQUIRED`
validation key. Similarly the form's `$error` object gets updated, but instead of boolean
`true` it contains an array of invalid widgets (widgets which fired `$invalid` event with
`REQUIRED` validation key).
- When the model is updated:
1. The model `$watch` listener assigns the model value to `$modelValue` on the widget.
2. The form then calls `$parseModel` method on widget if present. The method converts the
value to renderable format and assigns it to `$viewValue` (for example converting number to a
string.)
3. The form then emits a `$validate` which behaves as described above.
4. The form then calls `$render` method on the widget to update the DOM structure from the
`$viewValue`.
# Writing Your Own Widget
This example shows how to implement a custom HTML editor widget in Angular.
<doc:example>
<doc:source>
<script>
function EditorCntl() {
this.htmlContent = '<b>Hello</b> <i>World</i>!';
}
function HTMLEditorWidget(element) {
var self = this;
var htmlFilter = angular.filter('html');
this.$parseModel = function() {
// need to protect for script injection
try {
this.$viewValue = htmlFilter(
this.$modelValue || '').get();
if (this.$error.HTML) {
// we were invalid, but now we are OK.
this.$emit('$valid', 'HTML');
}
} catch (e) {
// if HTML not parsable invalidate form.
this.$emit('$invalid', 'HTML');
}
}
this.$render = function() {
element.html(this.$viewValue);
}
element.bind('keyup', function() {
self.$apply(function() {
self.$emit('$viewChange', element.html());
});
});
}
angular.directive('ng:html-editor-model', function() {
function linkFn($formFactory, element) {
var exp = element.attr('ng:html-editor-model'),
form = $formFactory.forElement(element),
widget;
element.attr('contentEditable', true);
widget = form.$createWidget({
scope: this,
model: exp,
controller: HTMLEditorWidget,
controllerArgs: [element]});
// if the element is destroyed, then we need to
// notify the form.
element.bind('$destroy', function() {
widget.$destroy();
});
}
linkFn.$inject = ['$formFactory'];
return linkFn;
});
</script>
<form name='editorForm' ng:controller="EditorCntl">
<div ng:html-editor-model="htmlContent"></div>
<hr/>
HTML: <br/>
<textarea ng:model="htmlContent" cols="80"></textarea>
<hr/>
<pre>editorForm = {{editorForm}}</pre>
</form>
</doc:source>
<doc:scenario>
it('should enter invalid HTML', function() {
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-valid/);
input('htmlContent').enter('<');
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
# HTML Inputs
The most common widgets you will use will be in the form of the
standard HTML set. These widgets are bound using the `name` attribute
to an expression. In addition, they can have `required` attribute to further control their
validation.
<doc:example>
<doc:source>
<script>
function Ctrl() {
this.input1 = '';
this.input2 = '';
this.input3 = 'A';
this.input4 = false;
this.input5 = 'c';
this.input6 = [];
}
</script>
<table style="font-size:.9em;" ng:controller="Ctrl">
<tr>
<th>Name</th>
<th>Format</th>
<th>HTML</th>
<th>UI</th>
<th ng:non-bindable>{{input#}}</th>
</tr>
<tr>
<th>text</th>
<td>String</td>
<td><tt>&lt;input type="text" ng:model="input1"&gt;</tt></td>
<td><input type="text" ng:model="input1" size="4"></td>
<td><tt>{{input1|json}}</tt></td>
</tr>
<tr>
<th>textarea</th>
<td>String</td>
<td><tt>&lt;textarea ng:model="input2"&gt;&lt;/textarea&gt;</tt></td>
<td><textarea ng:model="input2" cols='6'></textarea></td>
<td><tt>{{input2|json}}</tt></td>
</tr>
<tr>
<th>radio</th>
<td>String</td>
<td><tt>
&lt;input type="radio" ng:model="input3" value="A"&gt;<br>
&lt;input type="radio" ng:model="input3" value="B"&gt;
</tt></td>
<td>
<input type="radio" ng:model="input3" value="A">
<input type="radio" ng:model="input3" value="B">
</td>
<td><tt>{{input3|json}}</tt></td>
</tr>
<tr>
<th>checkbox</th>
<td>Boolean</td>
<td><tt>&lt;input type="checkbox" ng:model="input4"&gt;</tt></td>
<td><input type="checkbox" ng:model="input4"></td>
<td><tt>{{input4|json}}</tt></td>
</tr>
<tr>
<th>pulldown</th>
<td>String</td>
<td><tt>
&lt;select ng:model="input5"&gt;<br>
&nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select ng:model="input5">
<option value="c">C</option>
<option value="d">D</option>
</select>
</td>
<td><tt>{{input5|json}}</tt></td>
</tr>
<tr>
<th>multiselect</th>
<td>Array</td>
<td><tt>
&lt;select ng:model="input6" multiple size="4"&gt;<br>
&nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select ng:model="input6" multiple size="4">
<option value="e">E</option>
<option value="f">F</option>
</select>
</td>
<td><tt>{{input6|json}}</tt></td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should exercise text', function() {
input('input1').enter('Carlos');
expect(binding('input1')).toEqual('"Carlos"');
});
it('should exercise textarea', function() {
input('input2').enter('Carlos');
expect(binding('input2')).toEqual('"Carlos"');
});
it('should exercise radio', function() {
expect(binding('input3')).toEqual('"A"');
input('input3').select('B');
expect(binding('input3')).toEqual('"B"');
input('input3').select('A');
expect(binding('input3')).toEqual('"A"');
});
it('should exercise checkbox', function() {
expect(binding('input4')).toEqual('false');
input('input4').check();
expect(binding('input4')).toEqual('true');
});
it('should exercise pulldown', function() {
expect(binding('input5')).toEqual('"c"');
select('input5').option('d');
expect(binding('input5')).toEqual('"d"');
});
it('should exercise multiselect', function() {
expect(binding('input6')).toEqual('[]');
select('input6').options('e');
expect(binding('input6')).toEqual('["e"]');
select('input6').options('e', 'f');
expect(binding('input6')).toEqual('["e","f"]');
});
</doc:scenario>
</doc:example>
#Testing
When unit-testing a controller it may be desirable to have a reference to form and to simulate
different form validation states.
This example demonstrates a login form, where the login button is enabled only when the form is
properly filled out.
<pre>
<div ng:controller="LoginController">
<form name="loginForm">
<input type="text" ng:model="username" required/>
<input type="password" ng:model="password" required/>
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
</form>
</div>
</pre>
In the unit tests we do not have access to the DOM, and therefore the `loginForm` reference does
not get set on the controller. This example shows how it can be unit-tested, by creating a mock
form.
<pre>
function LoginController() {
this.disableLogin = function() {
return this.loginForm.$invalid;
};
}
describe('LoginController', function() {
it('should disable login button when form is invalid', function() {
var scope = angular.scope();
var loginController = scope.$new(LoginController);
// In production the 'loginForm' form instance gets set from the view,
// but in unit-test we have to set it manually.
loginController.loginForm = scope.$service('$formFactory')();
expect(loginController.disableLogin()).toBe(false);
// Now simulate an invalid form
loginController.loginForm.$emit('$invalid', 'MyReason');
expect(loginController.disableLogin()).toBe(true);
// Now simulate a valid form
loginController.loginForm.$emit('$valid', 'MyReason');
expect(loginController.disableLogin()).toBe(false);
});
});
</pre>
## Custom widgets
This example demonstrates a login form, where the password has custom validation rules.
<pre>
<div ng:controller="LoginController">
<form name="loginForm">
<input type="text" ng:model="username" required/>
<input type="@StrongPassword" ng:model="password" required/>
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
</form>
</div>
</pre>
In the unit tests we do not have access to the DOM, and therefore the `loginForm` and custom
input type reference does not get set on the controller. This example shows how it can be
unit-tested, by creating a mock form and a mock custom input type.
<pre>
function LoginController(){
this.disableLogin = function() {
return this.loginForm.$invalid;
};
this.StrongPassword = function(element) {
var widget = this;
element.attr('type', 'password'); // act as password.
this.$on('$validate', function(){
widget.$emit(widget.$viewValue.length > 5 ? '$valid' : '$invalid', 'PASSWORD');
});
};
}
describe('LoginController', function() {
it('should disable login button when form is invalid', function() {
var scope = angular.scope();
var loginController = scope.$new(LoginController);
var input = angular.element('<input>');
// In production the 'loginForm' form instance gets set from the view,
// but in unit-test we have to set it manually.
loginController.loginForm = scope.$service('$formFactory')();
// now instantiate a custom input type
loginController.loginForm.$createWidget({
scope: loginController,
model: 'password',
alias: 'password',
controller: loginController.StrongPassword,
controllerArgs: [input]
});
// Verify that the custom password input type sets the input type to password
expect(input.attr('type')).toEqual('password');
expect(loginController.disableLogin()).toBe(false);
// Now simulate an invalid form
loginController.loginForm.password.$emit('$invalid', 'PASSWORD');
expect(loginController.disableLogin()).toBe(true);
// Now simulate a valid form
loginController.loginForm.password.$emit('$valid', 'PASSWORD');
expect(loginController.disableLogin()).toBe(false);
// Changing model state, should also influence the form validity
loginController.password = 'abc'; // too short so it should be invalid
scope.$digest();
expect(loginController.loginForm.password.$invalid).toBe(true);
// Changeing model state, should also influence the form validity
loginController.password = 'abcdef'; // should be valid
scope.$digest();
expect(loginController.loginForm.password.$valid).toBe(true);
});
});
</pre>
+124
View File
@@ -0,0 +1,124 @@
@ngdoc overview
@name Developer Guide: i18n and l10n
@description
# I18n and L10n in AngularJS
**What is i18n and l10n?**
Internationalization, abbreviated i18n, is the process of developing products in such a way that
they can be localized for languages and cultures easily. Localization, abbreviated l10n, is the
process of adapting applications and text to enable their usability in a particular cultural or
linguistic market. For application developers, internationalizing an application means abstracting
all of the strings and other locale-specific bits (such as date or currency formats) out of the
application. Localizing an application means providing translations and localized formats for the
abstracted bits.
**What level of support for i18n/l10n is currently in Angular?**
Currently, Angular supports i18n/l10n for {@link
http://docs.angularjs.org/#!/api/angular.filter.date datetime}, {@link
http://docs.angularjs.org/#!/api/angular.filter.number number} and {@link
http://docs.angularjs.org/#!/api/angular.filter.currency currency} filters.
Additionally, Angular supports localizable pluralization support provided by the {@link
api/angular.widget.ng:pluralize ng:pluralize widget}.
All localizable Angular components depend on locale-specific rule sets managed by the {@link
api/angular.service.$locale $locale service}.
For readers who want to jump straight into examples, we have a few web pages that showcase how to
use Angular filters with various locale rule sets. You can find these examples either on {@link
https://github.com/angular/angular.js/tree/master/i18n/e2e Github} or in the i18n/e2e folder of
Angular development package.
**What is a locale id?**
A locale is a specific geographical, political, or cultural region. The most commonly used locale
ID consists of two parts: language code and country code. For example, en-US, en-AU, zh-CN are all
valid locale IDs that have both language codes and country codes. Because specifying a country code
in locale ID is optional, locale IDs such as en, zh, and sk are also valid. See the {@link
http://userguide.icu-project.org/locale ICU } website for more information about using locale IDs.
**Supported locales in Angular**
Angular separates number and datetime format rule sets into different files, each file for a
particular locale. You can find a list of currently supported locales {@link
https://github.com/angular/angular.js/tree/master/i18n/locale here}
# Providing locale rules to Angular
There are two approaches to providing locale rules to Angular:
**1. Pre-bundled rule sets**
You can pre-bundle the desired locale file with Angular by concatenating the content of the
locale-specific file to the end of `angular.js` or `angular.min.js` file.
For example on *nix, to create a an angular.js file that contains localization rules for german
locale, you can do the following:
`cat angular.js i18n/angular-locale_de-ge.js > angular_de-ge.js`
When the application containing `angular_de-ge.js` script instead of the generic angular.js script
starts, Angular is automatically pre-configured with localization rules for the german locale.
**2. Including locale js script in index.html page**
You can also include the locale specific js file in the index.html page. For example, if one client
requires German locale, you would serve index_de-ge.html which will look something like this:
<pre>
<html>
<head>
….
<script src="angular.js" ng:autobind></script>
<script src="i18n/angular-locale_de-ge.js"></script>
….
</head>
</html>
</pre>
**Comparison of the two approaches**
Both approaches described above requires you to prepare different index.html pages or js files for
each locale that your app may be localized into. You also need to configure your server to serve
the correct file that correspond to the desired locale.
However, the second approach (Including locale js script in index.html page) is likely to be slower
because an extra script needs to be loaded.
# "Gotchas"
**Currency symbol "gotcha"**
Angular's {@link http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} allows
you to use the default currency symbol from the {@link api/angular.service.$locale locale service},
or you can provide the filter with a custom currency symbol. If your app will be used only in one
locale, it is fine to rely on the default currency symbol. However, if you anticipate that viewers
in other locales might use your app, you should provide your own currency symbol to make sure the
actual value is understood.
For example, if you want to display account balance of 1000 dollars with the following binding
containing currency filter: `{{ 1000 | currency }}`, and your app is currently in en-US locale.
'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, her
browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
will really upset your client.
In this case, you need to override the default currency symbol by providing the {@link
http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} with a currency symbol as
a parameter when you configure the filter, for example, {{ 1000 | currency:"USD$"}}. This way,
Angular will always show a balance of 'USD$1000' and disregard any locale changes.
**Translation length "gotcha"**
Keep in mind that translated strings/datetime formats can vary greatly in length. For example,
`June 3, 1977` will be translated to Spanish as `3 de junio de 1977`. There are bound to be other
more extreme cases. Hence, when internationalizing your apps, you need to apply CSS rules
accordingly and do thorough testing to make sure UI components do not overlap.
**Timezones**
Keep in mind that Angular datetime filter uses the time zone settings of the browser. So the same
application will show different time information depending on the time zone settings of the
computer that the application is running on. Neither Javascript nor Angular currently supports
displaying the date with a timezone specified by the developer.
@@ -0,0 +1,42 @@
@ngdoc overview
@name Developer Guide: Introduction
@description
Angular is pure client-side technology, written entirely in JavaScript. It works with the
long-established technologies of the web (HTML, CSS, and JavaScript) to make the development of web
apps easier and faster than ever before.
One important way that angular simplifies web development is by increasing the level of abstraction
between the developer and most low-level web app development tasks. Angular automatically takes
care of many of these tasks, including:
* DOM Manipulation
* Setting Up Listeners and Notifiers
* Input Validation
Because angular handles much of the work involved in these tasks, developers can concentrate more
on application logic and less on repetitive, error-prone, lower-level coding.
At the same time that angular simplifies the development of web apps, it brings relatively
sophisticated techniques to the client-side, including:
* Separation of data, application logic, and presentation components
* Data Binding between data and presentation components
* Services (common web app operations, implemented as substitutable objects)
* Dependency Injection (used primarily for wiring together services)
* An extensible HTML compiler (written entirely in JavaScript)
* Ease of Testing
These techniques have been for the most part absent from the client-side for far too long.
## Single-page / Round-trip Applications
You can use angular to develop both single-page and round-trip apps, but angular is designed
primarily for developing single-page apps. Angular supports browser history, forward and back
buttons, and bookmarking in single-page apps.
You normally wouldn't want to load angular with every page change, as would be the case with using
angular in a round-trip app. However, it would make sense to do so if you were adding a subset of
angular's features (for example, templates to leverage angular's data-binding feature) to an
existing round-trip app. You might follow this course of action if you were migrating an older app
to a single-page angular app.
+24
View File
@@ -0,0 +1,24 @@
@ngdoc overview
@name Developer Guide: About MVC in Angular
@description
While Model-View-Controller (MVC) has acquired different shades of meaning over the years since it
first appeared, angular incorporates the basic principles behind the original {@link
http://en.wikipedia.org/wiki/Modelviewcontroller MVC} software design pattern into its way of
building client-side web applications.
The MVC pattern greatly summarized:
* Separate applications into distinct presentation, data, and logic components
* Encourage loose coupling between these components
Along with {@link dev_guide.services services} and {@link dev_guide.di dependency injection}, MVC
makes angular applications better structured, easier to maintain and more testable.
The following topics explain how angular incorporates the MVC pattern into the angular way of
developing web applications:
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
@@ -0,0 +1,259 @@
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the Controller Component
@description
In angular, a controller is a JavaScript function(type/class) that is used to augment instances of
angular {@link dev_guide.scopes Scope}, excluding the root scope. When you or angular create a new
child scope object via the {@link api/angular.scope.$new scope.$new} API , there is an
option to pass in a controller as a method argument. This will tell angular to associate the
controller with the new scope and to augment its behavior.
Use controllers to:
- Set up the initial state of a scope object.
- Add behavior to the scope object.
# Setting up the initial state of a scope object
Typically, when you create an application you need to set up an initial state for an angular scope.
Angular applies (in the sense of JavaScript's `Function#apply`) the controller constructor function
to a new angular scope object, which sets up an initial scope state. This means that angular never
creates instances of the controller type (by invoking the `new` operator on the controller
constructor). Constructors are always applied to an existing scope object.
You set up the initial state of a scope by creating model properties. For example:
function GreetingCtrl() {
this.greeting = 'Hola!';
}
The `GreetingCtrl` controller creates a `greeting` model which can be referred to in a template.
When a controller function is applied to an angular scope object, the `this` of the controller
function becomes the scope of the angular scope object, so any assignment to `this` within the
controller function happens on the angular scope object.
# Adding Behavior to a Scope Object
Behavior on an angular scope object is in the form of scope method properties available to the
template/view. This behavior interacts with and modifies the application model.
As discussed in the {@link dev_guide.mvc.understanding_model Model} section of this guide, any
objects (or primitives) assigned to the scope become model properties. Any functions assigned to
the scope, along with any prototype methods of the controller type, become functions available in
the template/view, and can be invoked via angular expressions and `ng:` event handlers (e.g. {@link
api/angular.directive.ng:click ng:click}). These controller methods are always evaluated within the
context of the angular scope object that the controller function was applied to (which means that
the `this` keyword of any controller method is always bound to the scope that the controller
augments). This is how the second task of adding behavior to the scope is accomplished.
# Using Controllers Correctly
In general, a controller shouldn't try to do too much. It should contain only the business logic
needed for a single view.
The most common way to keep controllers slim is by encapsulating work that doesn't belong to
controllers into services and then using these services in controllers via dependency injection.
This is discussed in the {@link dev_guide.di Dependency Injection} {@link dev_guide.services
Services} sections of this guide.
Do not use controllers for:
- Any kind of DOM manipulation — Controllers should contain only business logic. DOM
manipulation—the presentation logic of an application—is well known for being hard to test.
Putting any presentation logic into controllers significantly affects testability of the business
logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If
you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link
dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}.
- Input formatting — Use {@link dev_guide.forms angular form widgets} instead.
- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.
- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular
services} instead.
- Instantiate or manage the life-cycle of other components (for example, to create service
instances).
# Associating Controllers with Angular Scope Objects
You can associate controllers with scope objects explicitly via the {@link api/angular.scope.$new
scope.$new} api or implicitly via the {@link api/angular.directive.ng:controller ng:controller
directive} or {@link api/angular.service.$route $route service}.
## Controller Constructor and Methods Example
To illustrate how the controller component works in angular, let's create a little app with the
following components:
- A {@link dev_guide.templates template} with two buttons and a simple message
- A model consisting of a string named `spice`
- A controller with two functions that set the value of `spice`
The message in our template contains a binding to the `spice` model, which by default is set to the
string "very". Depending on which button is clicked, the `spice` model is set to `chili` or
`jalapeño`, and the message is automatically updated by data-binding.
## A Spicy Controller Example
<pre>
<body ng:controller="SpicyCtrl">
<button ng:click="chiliSpicy()">Chili</button>
<button ng:click="jalapenoSpicy()">Jalapeño</button>
<p>The food is {{spice}} spicy!</p>
</body>
function SpicyCtrl() {
this.spice = 'very';
this.chiliSpicy = function() {
this.spice = 'chili';
}
}
SpicyCtrl.prototype.jalapenoSpicy = function() {
this.spice = 'jalapeño';
}
</pre>
Things to notice in the example above:
- The `ng:controller` directive is used to (implicitly) create a scope for our template, and the
scope is augmented (managed) by the `SpicyCtrl` controller.
- `SpicyCtrl` is just a plain JavaScript function. As an (optional) naming convention the name
starts with capital letter and ends with "Ctrl" or "Controller".
- The JavaScript keyword `this` in the `SpicyCtrl` function is bound to the scope that the
controller augments.
- Assigning a property to `this` creates or updates the model.
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or
as prototype methods of the controller constructor function(the `jalapenoSpicy` method)
- Both controller methods are available in the template (for the `body` element and and its
children).
Controller methods can also take arguments, as demonstrated in the following variation of the
previous example.
## Controller Method Arguments Example
<pre>
<body ng:controller="SpicyCtrl">
<input ng:model="customSpice" value="wasabi">
<button ng:click="spicy('chili')">Chili</button>
<button ng:click="spicy(customSpice)">Custom spice</button>
<p>The food is {{spice}} spicy!</p>
</body>
function SpicyCtrl() {
this.spice = 'very';
this.spicy = function(spice) {
this.spice = spice;
}
}
</pre>
Notice that the `SpicyCtrl` controller now defines just one method called `spicy`, which takes one
argument called `spice`. The template then refers to this controller method and passes in a string
constant `'chili'` in the binding for the first button and a model property `spice` (bound to an
input box) in the second button.
## Controller Inheritance Example
Controller inheritance in angular is based on {@link api/angular.scope Scope} inheritance. Let's
have a look at an example:
<pre>
<body ng:controller="MainCtrl">
<p>Good {{timeOfDay}}, {{name}}!</p>
<div ng:controller="ChildCtrl">
<p>Good {{timeOfDay}}, {{name}}!</p>
<p ng:controller="BabyCtrl">Good {{timeOfDay}}, {{name}}!</p>
</body>
function MainCtrl() {
this.timeOfDay = 'morning';
this.name = 'Nikki';
}
function ChildCtrl() {
this.name = 'Mattie';
}
function BabyCtrl() {
this.timeOfDay = 'evening';
this.name = 'Gingerbreak Baby';
}
</pre>
Notice how we nested three `ng:controller` directives in our template. This template construct will
result in 4 scopes being created for our view:
- The root scope
- The `MainCtrl` scope, which contains `timeOfDay` and `name` models
- The `ChildCtrl` scope, which shadows the `name` model from the previous scope and inherits the
`timeOfDay` model
- The `BabyCtrl` scope, which shadows both the `timeOfDay` model defined in `MainCtrl` and `name`
model defined in the ChildCtrl
Inheritance works between controllers in the same way as it does with models. So in our previous
examples, all of the models could be replaced with controller methods that return string values.
Note: Standard prototypical inheritance between two controllers doesn't work as one might expect,
because as we mentioned earlier, controllers are not instantiated directly by angular, but rather
are applied to the scope object.
## Testing Controllers
The way to test a controller depends upon how complicated the controller is.
- If your controller doesn't use DI or scope methods — create the controller with the `new`
operator and test away. For example:
Controller Function:
<pre>
function myController() {
this.spices = [{"name":"pasilla", "spiciness":"mild"},
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
this.spice = "habanero";
}
</pre>
Controller Test:
<pre>
describe('myController function', function() {
describe('myController', function() {
var ctrl;
beforeEach(function() {
ctrl = new myController();
});
it('should create "spices" model with 3 spices', function() {
expect(ctrl.spices.length).toBe(3);
});
it('should set the default value of spice', function() {
expect(ctrl.spice).toBe('habanero');
});
});
});
</pre>
- If your controller does use DI or scope methods — create a root scope, then create the controller
in the root scope with `scope.$new(MyController)`. Test the controller using `$eval`, if necessary.
- If you need to test a nested controller that depends on its parent's state — create a root scope,
create a parent scope, create a child scope, and test the controller using $eval if necessary.
## Related Topics
* {@link dev_guide.mvc About MVC in Angular}
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
@@ -0,0 +1,71 @@
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the Model Component
@description
Depending on the context of the discussion in angular documentation, the term _model_ can refer to
either a single object representing one entity (for example, a model called "phones" with its value
being an array of phones) or the entire data model for the application (all entities).
In angular, a model is any data that is reachable as a property of an angular {@link
dev_guide.scopes Scope} object. The name of the property is the model identifier and the value is
any JavaScript object (including arrays and primitives).
The only requirement for a JavaScript object to be a model in angular is that the object must be
referenced by an angular scope as a property of that scope object. This property reference can be
created explicitly or implicitly.
You can create models by explicitly creating scope properties referencing JavaScript objects in the
following ways:
* Make a direct property assignment to the scope object in JavaScript code; this most commonly
occurs in controllers:
function MyCtrl() {
// create property 'foo' on the MyCtrl's scope
// and assign it an initial value 'bar'
this.foo = 'bar';
}
* Use an {@link dev_guide.expressions angular expression} with an assignment operator in templates:
<button ng:click="{{foos='ball'}}">Click me</button>
* Use {@link api/angular.directive.ng:init ng:init directive} in templates (for toy/example apps
only, not recommended for real applications):
<body ng:init=" foo = 'bar' ">
Angular creates models implicitly (by creating a scope property and assigning it a suitable value)
when processing the following template constructs:
* Form input, select, textarea and other form elements:
<input ng:model="query" value="fluffy cloud">
The code above creates a model called "query" on the current scope with the value set to "fluffy
cloud".
* An iterator declaration in {@link api/angular.widget.@ng:repeat ng:repeater}:
<p ng:repeat="phone in phones"></p>
The code above creates one child scope for each item in the "phones" array and creates a "phone"
object (model) on each of these scopes with its value set to the value of "phone" in the array.
In angular, a JavaScript object stops being a model when:
* No angular scope contains a property that references the object.
* All angular scopes that contain a property referencing the object become stale and eligible for
garbage collection.
The following illustration shows a simple data model created implicitly from a simple template:
<img src="img/guide/about_model_final.png">
## Related Topics
* {@link dev_guide.mvc About MVC in Angular}
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
@@ -0,0 +1,22 @@
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the View Component
@description
In angular, the view is the DOM loaded and rendered in the browser, after angular has transformed
the DOM based on information in the template, controller and model.
<img src="img/guide/about_view_final.png">
In the angular implementation of MVC, the view has knowledge of both the model and the controller.
The view knows about the model where two-way data-binding occurs. The view has knowledge of the
controller through angular directives, such as {@link api/angular.directive.ng:controller
ng:controller} and {@link api/angular.widget.ng:view ng:view}, and through bindings of this form:
`{{someControllerFunction()}}`. In these ways, the view can call functions in an associated
controller function.
## Related Topics
* {@link dev_guide.mvc About MVC in Angular}
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
+242
View File
@@ -0,0 +1,242 @@
@ngdoc overview
@name Developer Guide: Overview
@description
# What Is Angular?
The short answer: angular is a new, powerful, client-side technology that makes it much easier for
you to create dynamic web sites and complex web apps, all without leaving the comfort of your HTML
/ JavaScript home.
The long answer: it depends on where you're coming from...
* If you're a web designer, you might perceive angular to be a sweet {@link dev_guide.templates
templating} system, that doesn't get in your way and provides you with lots of nice built-ins that
make it easier to do what you want to do.
* If you're a web developer, you might be thrilled that angular functions as an excellent web
framework, one that assists you all the way through the development cycle.
* If you want to go deeper, you can immerse yourself in angular's extensible HTML {@link
dev_guide.compiler compiler} that runs in your browser. The angular compiler teaches your browser
new tricks.
Angular is not just a templating system, but you can create fantastic templates with it. Angular is
not just a web framework, but it features a very nice framework. Angular is not just an extensible
HTML compiler, but the compiler is at the core of Angular. Angular includes all of these
components, along with others. Angular is far greater than the sum of its parts. It is a new,
better way to develop web applications!
## An Introductory Angular Example
Let's say that you are a web designer, and you've spent many thous — erm, hundreds of hours
designing web sites. But at this point, the thought of manipulating the DOM, writing listeners and
input validators, all just to implement a simple form? No. You either don't want to go there in
the first place or you've been there and the thrill is gone.
So look over the following simple example written using angular. Note that it features only the
templating aspect of angular, but this should suffice for now to quickly demonstrate how much
easier a web developer's life can if they're using angular:
<doc:example>
<doc:source>
<script>
function InvoiceCntl() {
this.qty = 1;
this.cost = 19.95;
}
</script>
<div ng:controller="InvoiceCntl">
<b>Invoice:</b>
<br />
<br />
<table>
<tr><td> </td><td> </td>
<tr><td>Quantity</td><td>Cost</td></tr>
<tr>
<td><input type="integer" min="0" ng:model="qty" required ></td>
<td><input type="number" ng:model="cost" required ></td>
</tr>
</table>
<hr />
<b>Total:</b> {{qty * cost | currency}}
</div>
</doc:source>
<!--
<doc:scenario>
it('should show of angular binding', function() {
expect(binding('qty * cost')).toEqual('$19.95');
input('qty').enter('2');
input('cost').enter('5.00');
expect(binding('qty * cost')).toEqual('$10.00');
});
</doc:scenario>
-->
</doc:example>
Try out the Live Preview above, and then let's walk through the example and describe what's going
on.
In the `<html>` tag, we add an attribute to let the browser know about the angular namespace:
<html xmlns:ng="http://angularjs.org">
This ensures angular runs nicely in all major browsers.
In the `<script>` tag we do two angular setup tasks:
1. We load `angular.js`.
2. The angular {@link api/angular.directive.ng:autobind ng:autobind} directive tells angular to
{@link dev_guide.compiler compile} and manage the whole HTML document.
`<script src="http://code.angularjs.org/0.9.15/angular-0.9.15.min.js"
ng:autobind></script>`
From the `name` attribute of the `<input>` tags, angular automatically sets up two-way data
binding, and we also demonstrate some easy input validation:
Quantity: <input type="integer" min="0" ng:model="qty" required >
Cost: <input type="number" ng:model="cost" required >
These input widgets look normal enough, but consider these points:
* When this page loaded, angular bound the names of the input widgets (`qty` and `cost`) to
variables of the same name. Think of those variables as the "Model" component of the
Model-View-Controller design pattern.
* Note the angular/HTML widget, {@link api/angular.widget.input input}.
You may have noticed that when you enter invalid data
or leave the the input fields blank, the borders turn red color, and the display value disappears.
These widgets make it easier to implement field validation than coding them in JavaScript,
no? Yes.
And finally, the mysterious `{{ double curly braces }}`:
Total: {{qty * cost | currency}}
This notation, `{{ _expression_ }}`, is a bit of built-in angular {@link dev_guide.compiler.markup
markup}, a shortcut for displaying data to the user. The expression within curly braces gets
transformed by the angular compiler into an angular directive ({@link api/angular.directive.ng:bind
ng:bind}). The expression itself can be a combination of both an expression and a {@link
dev_guide.templates.filters filter}: `{{ expression | filter }}`. Angular provides filters for
formatting display data.
In the example above, the expression in double-curly braces directs angular to, "Bind the data we
got from the input widgets to the display, multiply them together, and format the resulting number
into output that looks like money."
# The Angular Philosophy
Angular is built around the belief that declarative code is better than imperative when it comes to
building UIs and wiring software components together, while imperative code is excellent for
expressing business logic.
Not to put too fine a point on it, but if you wanted to add a new label to your application, you
could do so by simply adding text to the HTML template, saving the code, and refreshing your
browser:
<pre>
<span class="label">Hello</span>
</pre>
Or, as in programmatic systems (like {@link http://code.google.com/webtoolkit/ GWT}), you would
have to write the code and then run the code like this:
<pre>
var label = new Label();
label.setText('Hello');
label.setClass('label');
parent.addChild(label);
</pre>
That's one line of markup versus four times as much code.
## More Angular Philosophy
* It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves
the testability of the code.
* It is a really, _really_ good idea to regard app testing as equal in importance to app writing.
Testing difficulty is dramatically affected by the way the code is structured.
* It is an excellent idea to decouple the client side of an app from the server side. This allows
development work to progress in parallel, and allows for reuse of both sides.
* It is very helpful indeed if the framework guides developers through the entire journey of
building an app: from designing the UI, through writing the business logic, to testing.
* It is always good to make common tasks trivial and difficult tasks possible.
Now that we're homing in on what angular is, perhaps now would be a good time to list a few things
that angular is not:
* It's not a Library. You don't just call its functions, although it does provide you with some
utility APIs.
* It's not a DOM Manipulation Library. Angular uses jQuery to manipulate the DOM behind the scenes,
rather than give you functions to manipulate the DOM yourself.
* It's not a Widget Library. There are lots of existing widget libraries that you can integrate
with angular.
* It's not "Just Another Templating System". A part of angular is a templating system. The
templating subsystem of angular is different from the traditional approach for these reasons:
* It Uses HTML/CSS syntax: This makes it easy to read and can be edited with existing HTML/CSS
authoring tools.
* It Extends HTML vocabulary: Angular allows you to create new HTML tags, which expand into
dynamic UI components.
* It Executes in the browser: Removes the round trip to the server for many operations and
creates instant feedback for users as well as developers.
* It Has Bidirectional data binding: The model is the single source of truth. Programmatic
changes to the model are automatically reflected in the view. Any changes by the user to the view
are automatically reflected in the model.
# Why You Want Angular
Angular frees you from the following pain:
* **Registering callbacks:** Registering callbacks clutters your code, making it hard to see the
forest for the trees. Removing common boilerplate code such as callbacks is a good thing. It vastly
reduces the amount of JavaScript coding _you_ have to do, and it makes it easier to see what your
application does.
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
applications, but it's cumbersome and error-prone. By declaratively describing how the UI should
change as your application state changes, you are freed from low level DOM manipulation tasks. Most
applications written with angular never have to programatically manipulate the DOM, although you
can if you want to.
* **Marshaling data to and from the UI:** CRUD operations make up the majority of AJAX
applications. The flow of marshaling data from the server to an internal object to an HTML form,
allowing users to modify the form, validating the form, displaying validation errors, returning to
an internal model, and then back to the server, creates a lot of boilerplate code. Angular
eliminates almost all of this boilerplate, leaving code that describes the overall flow of the
application rather than all of the implementation details.
* **Writing tons of initialization code just to get started:** Typically you need to write a lot of
plumbing just to get a basic "Hello World" AJAX app working. With angular you can bootstrap your
app easily using services, which are auto-injected into your application in a {@link
http://code.google.com/p/google-guice/ Guice}-like dependency-injection style. This allows you to
get started developing features quickly. As a bonus, you get full control over the initialization
process in automated tests.
# Watch a Presentation About Angular
Here is an early presentation on angular, but note that substantial development has occurred since
the talk was given in July of 2010.
<object width="480" height="385">
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"></param>
<param name="allowFullScreen" value="true"></param>
<param name="allowscriptaccess" value="always"></param>
<embed src="http://www.youtube.com/v/elvcgVSynRg&amp;hl=en_US&amp;fs=1"
type="application/x-shockwave-flash" allowscriptaccess="always"
allowfullscreen="true" width="480" height="385"></embed>
</object>
{@link
https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID
Presentation}
|
{@link
https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO
Source}
@@ -0,0 +1,225 @@
@ngdoc overview
@name Developer Guide: Scopes: Scope Internals
@description
## What is a scope?
A scope is an execution context for {@link dev_guide.expressions expressions}. You can think of a
scope as a JavaScript object that has an extra set of APIs for registering change listeners and for
managing its own life cycle. In Angular's implementation of the model-view-controller design
pattern, a scope's properties comprise both the model and the controller methods.
### Scope characteristics
- Scopes provide APIs ({@link api/angular.scope.$watch $watch}) to observe model mutations.
- Scopes provide APIs ({@link api/angular.scope.$apply $apply}) to propagate any model changes
through the system into the view from outside of the "Angular realm" (controllers, services,
Angular event handlers).
- Scopes can be nested to isolate application components while providing access to shared model
properties. A scope (prototypically) inherits properties from its parent scope.
- In some parts of the system (such as controllers, services and directives), the scope is made
available as `this` within the given context. (Note: This api will change before 1.0 is released.)
### Root scope
Every application has a root scope, which is the ancestor of all other scopes. The root scope is
responsible for creating the injector which is assigned to the {@link api/angular.scope.$service
$service} property, and initializing the services.
### What is scope used for?
{@link dev_guide.expressions Expressions} in the view are {@link api/angular.scope.$eval evaluated}
against the current scope. When HTML DOM elements are attached to a scope, expressions in those
elements are evaluated against the attached scope.
There are two kinds of expressions:
- Binding expressions, which are observations of property changes. Property changes are reflected
in the view during the {@link api/angular.scope.$digest digest cycle}.
- Action expressions, which are expressions with side effects. Typically, the side effects cause
execution of a method in a controller in response to a user action, such as clicking on a button.
### Scope inheritance
A scope (prototypically) inherits properties from its parent scope. Since a given property may not
reside on a child scope, if a property read does not find the property on a scope, the read will
recursively check the parent scope, grandparent scope, etc. all the way to the root scope before
defaulting to undefined.
{@link api/angular.directive Directives} associated with elements (ng:controller, ng:repeat,
ng:include, etc.) create new child scopes that inherit properties from the current parent scope.
Any code in Angular is free to create a new scope. Whether or not your code does so is an
implementation detail of the directive, that is, you can decide when or if this happens.
Inheritance typically mimics HTML DOM element nesting, but does not do so with the same
granularity.
A property write will always write to the current scope. This means that a write can hide a parent
property within the scope it writes to, as shown in the following example.
<pre>
var root = angular.scope();
var child = root.$new();
root.name = 'angular';
expect(child.name).toEqual('angular');
expect(root.name).toEqual('angular');
child.name = 'super-heroic framework';
expect(child.name).toEqual('super-heroic framework');
expect(root.name).toEqual('angular');
</pre>
### Scope life cycle
1. **Creation**
* You can create the root scope via {@link api/angular.scope angular.scope()}.
* To create a child scopes, you should call {@link api/angular.scope.$new parentScope.$new()}.
2. **Watcher registration**
Watcher registration can happen at any time and on any scope (root or child) via {@link
api/angular.scope.$watch scope.$watch()} API.
3. **Model mutation**
For mutations to be properly observed, you should make them only within the execution of the
function passed into {@link api/angular.scope.$apply scope.$apply()} call. (Angular apis do this
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers, or
asynchronous work with {@link api/angular.service.$xhr $xhr} or {@link api/angular.service.$defer
$defer} services.
4. **Mutation observation**
At the end of each `$apply` call {@link api/angular.scope.$digest $digest} cycle is started on
the root scope, which then propagates throughout all child scopes.
During the `$digest` cycle, all `$watch-ers` expressions or functions are checked for model
mutation and if a mutation is detected, the `$watch-er` listener is called.
5. **Scope destruction**
When child scopes are no longer needed, it is the responsibility of the child scope creator to
destroy them via {@link api/angular.scope.$destroy scope.$destroy()} API. This will stop
propagation of `$digest` calls into the child scope and allow for memory used by the child scope
models to be reclaimed by the garbage collector.
The root scope can't be destroyed via the `$destroy` API. Instead, it is enough to remove all
references from your application to the scope object and garbage collector will do its magic.
## Scopes in Angular applications
To understand how Angular applications work, you need to understand how scopes work within an
application. This section describes the typical life cycle of an application so you can see how
scopes come into play throughout and get a sense of their interactions.
### How scopes interact in applications
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
element.
1. The root scope creates an {@link api/angular.injector injector} which is assigned to the
{@link api/angular.scope.$service $service} property of the root scope.
2. Any eager {@link api/angular.scope.$service services} are initialized at this point.
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
api/angular.directive directives} against the DOM template. The directives usually fall into one of
two categories:
- Observing {@link api/angular.directive directives}, such as double-curly expressions
`{{expression}}`, register listeners using the {@link api/angular.scope.$watch $watch()} method.
This type of directive needs to be notified whenever the expression changes so that it can update
the view.
- Listener directives, such as {@link api/angular.directive.ng:click ng:click}, register a
listener with the DOM. When the DOM listener fires, the directive executes the associated
expression and updates the view using the {@link api/angular.scope.$apply $apply()} method.
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
dev_guide.expressions expression} must be applied to the scope through the {@link
api/angular.scope.$apply $apply()} method so that all listeners are updated correctly.
### Directives that create scopes
In most cases, {@link api/angular.directive directives} and scopes interact but do not create new
instances of scope. However, some directives, such as {@link api/angular.directive.ng:controller
ng:controller} and {@link api/angular.widget.@ng:repeat ng:repeat}, create new child scopes using
the {@link api/angular.scope.$new $new()} method and then attach the child scope to the
corresponding DOM element. You can retrieve a scope for any DOM element by using an
`angular.element(aDomElement).scope()` method call.)
### Controllers and scopes
Scopes and controllers interact with each other in the following situations:
- Controllers use scopes to expose controller methods to templates (see {@link
api/angular.directive.ng:controller ng:controller}).
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
- Controllers may register {@link api/angular.scope.$watch watches} on the model. These watches
execute immediately after the controller behavior executes, but before the DOM gets updated.
See the {@link dev_guide.mvc.understanding_controller controller docs} for more information.
### Updating scope properties
You can update a scope by calling its {@link api/angular.scope.$apply $apply()} method with an
expression or a function as the function argument. However it is typically not necessary to do this
explicitly. In most cases, angular intercepts all external events (such as user interactions, XHRs,
and timers) and wraps their callbacks into the `$apply()` method call on the scope object for you
at the right time. The only time you might need to call `$apply()` explicitly is when you create
your own custom asynchronous widget or service.
The reason it is unnecessary to call `$apply()` from within your controller functions when you use
built-in angular widgets and services is because your controllers are typically called from within
an `$apply()` call already.
When a user inputs data, angularized widgets invoke `$apply()` on the current scope and evaluate an
angular expression or execute a function on this scope. Afterwards `$apply` will trigger `$digest`
call on the root scope, to propagate your changes through the entire system, which results in
$watch-ers firing and view getting updated. Similarly, when a request to fetch data from a server
is made and the response comes back, the data is written into the model (scope) within an $apply,
which then pushes updates through to the view and any other dependents.
A widget that creates scopes (such as {@link api/angular.widget.@ng:repeat ng:repeat}) via `$new`,
doesn't need to worry about propagating the `$digest` call from the parent scope to child scopes.
This happens automatically.
## Scopes in unit-testing
You can create scopes, including the root scope, in tests using the {@link api/angular.scope
angular.scope()} API. This allows you to mimic the run-time environment and have full control over
the life cycle of the scope so that you can assert correct model transitions. Since these scopes
are created outside the normal compilation process, their life cycles must be managed by the test.
### Using scopes in unit-testing
The following example demonstrates how the scope life cycle needs to be manually triggered from
within the unit-tests.
<pre>
// example of a test
var scope = angular.scope();
scope.$watch('name', function(scope, name){
scope.greeting = 'Hello ' + name + '!';
});
scope.name = 'angular';
// The watch does not fire yet since we have to manually trigger the digest phase.
expect(scope.greeting).toEqual(undefined);
// manually trigger digest phase from the test
scope.$digest();
expect(scope.greeting).toEqual('Hello Angular!');
</pre>
### Dependency injection in Tests
When you find it necessary to inject your own mocks in your tests, use a scope to override the
service instances, as shown in the following example.
<pre>
var myLocation = {};
var scope = angular.scope(angular.service, {$location: myLocation});
expect(scope.$service('$location')).toEqual(myLocation);
</pre>
## Related Topics
* {@link dev_guide.scopes Angular Scope Objects}
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
## Related API
* {@link api/angular.scope Angular Scope API}
+35
View File
@@ -0,0 +1,35 @@
@ngdoc overview
@name Developer Guide: Scopes
@description
An Angular scope is a JavaScript object with additional APIs useful for watching property changes,
Angular scope is the model in Model-View-Controller paradigm. Instances of scope serve as the
context within which all {@link dev_guide.expressions expressions} get evaluated.
You can think of Angular scope objects as the medium through which the model, view, and controller
communicate. Scopes are linked during the compilation process with the view. This linkage provides
the contexts in which Angular creates data-bindings between the model and the view.
In addition to providing the context in which data is evaluated, Angular scope objects watch for
model changes. The scope objects also notify all components interested in any model changes (for
example, functions registered through {@link api/angular.scope.$watch $watch}, bindings created by
{@link api/angular.directive.ng:bind ng:bind}, or HTML input elements).
Angular scope objects:
* Link the model, controller and view template together.
* Provide the mechanism to watch for model changes ({@link api/angular.scope.$watch $watch}).
* Apply model changes to the system ({@link api/angular.scope.$apply $apply}).
* Provide the context in which expressions are evaluated ({@link api/angular.scope.$eval $eval}).
## Related Topics
* {@link dev_guide.scopes.understanding_scopes Understanding Scopes}
* {@link dev_guide.scopes.internals Scopes Internals}
## Related API
* {@link api/angular.scope Angular Scope API}
@@ -0,0 +1,66 @@
@ngdoc overview
@name Developer Guide: Scopes: Understanding Scopes
@description
Angular automatically creates a root scope during initialization, and attaches it to the page's
root DOM element (usually `<html>`). The root scope object, along with any of its child scope
objects, serves as the infrastructure on which your data model is built. The data model (JavaScript
objects, arrays, or primitives) is attached to angular scope properties. Angular binds the property
values to the DOM where bindings are specified in the template. Angular attaches any controller
functions you have created to their respective scope objects.
<img src="img/guide/simple_scope_final.png">
Angular scopes can be nested, so a child scope has a parent scope upstream in the DOM. When you
display an angular expression in the view, angular walks the DOM tree looking in the closest
attached scope object for the specified data. If it doesn't find the data in the closest attached
scope, it looks further up the scope hierarchy until it finds the data.
A child scope object inherits properties from its parents. For example, in the following snippet of
code, observe how the value of `name` changes, based on the HTML element it is displayed in:
<doc:example>
<doc:source>
<ul ng:init="name='Hank'; names=['Igor', 'Misko', 'Gail', 'Kai']">
<li ng:repeat="name in names">
Name = {{name}}!
</li>
</ul>
<pre>Name={{name}}</pre>
</doc:source>
<doc:scenario>
it('should override the name property', function() {
expect(using('.doc-example-live').repeater('li').row(0)).
toEqual(['Igor']);
expect(using('.doc-example-live').repeater('li').row(1)).
toEqual(['Misko']);
expect(using('.doc-example-live').repeater('li').row(2)).
toEqual(['Gail']);
expect(using('.doc-example-live').repeater('li').row(3)).
toEqual(['Kai']);
expect(using('.doc-example-live').element('pre').text()).
toBe('Name=Hank');
});
</doc:scenario>
</doc:example>
The angular {@link api/angular.widget.@ng:repeat ng:repeat} directive creates a new scope for each
element that it repeats (in this example the elements are list items). In the `<ul>` element, we
initialized `name` to "Hank", and we created an array called `names` to use as the data source for
the list items. In each `<li>` element, `name` is overridden. Outside of the `<li>` repeater, the
original value of `name` is displayed.
The following illustration shows the DOM and angular scopes for the example above:
<img src="img/guide/dom_scope_final.png">
## Related Topics
* {@link dev_guide.scopes Angular Scope Objects}
* {@link dev_guide.scopes.internals Scopes Internals}
## Related API
* {@link api/angular.scope Angular Scope API}
@@ -0,0 +1,642 @@
@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 `$locationConfig` 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('$locationConfig', 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>
There is a special `replace` method which can be used to tell the $location service that the next
time the $location service is synced with the browser, the last history record should be replaced
instead of creating a new one. This is useful when you want to implement redirection, which would
otherwise break the back button (navigating back would retrigger the redirection). To change the
current URL without creating a new browser history record you can call:
<pre>
$location.path('/someNewPath');
$location.replace();
// or you can chain these as: $location.path('/someNewPath').replace();
</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 `$digest` phase. Since
multiple changes to the $location's state will be pushed to the browser as a single change, it's
enough to call the `replace()` method just once to make the entire "commit" a replace operation
rather than addition to the browser history. Once the browser is updated, the $location service
resets the flag set by `replace()` method and future mutations will create new history records,
unless `replace()` is called again.
### 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('$locationConfig', 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('$locationConfig', 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, {
$locationConfig: {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" ng:model="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}
@@ -0,0 +1,58 @@
@ngdoc overview
@name Developer Guide: Angular Services: Creating Angular Services
@description
While angular offers several useful services, for any nontrivial application you'll find it useful
to write your own custom services. To do this you begin by registering a service factory function
that angular's DI will use to create the service object when it is needed.
The `angular.service` method accepts three parameters:
- `{string} name` - Name of the service.
- `{function()} factory` - Factory function(called just once by DI).
- `{Object} config` - Configuration object with the following properties:
- `$inject` - {Array.<string>} - Array of service ids this service depends on. These services
will be passed as arguments into the factory function in the same order specified in the `$inject`
array. Defaults to `[]`.
- `$eager` - {boolean} - If true, the service factory will be called and the service will be
instantiated when angular boots. If false, the service will be lazily instantiated when it is first
requested during instantiation of a dependant. Defaults to `false`.
The `this` of the factory function is bound to the root scope of the angular application.
All angular services participate in {@link dev_guide.di dependency injection (DI)} by registering
themselves with angular's DI system (injector) under a `name` (id) as well as by declaring
dependencies which need to be provided for the factory function of the registered service. The
ability to swap dependencies for mocks/stubs/dummies in tests allows for services to be highly
testable.
Following is an example of a very simple service. This service depends on the `$window` service
(which is passed as a parameter to the factory function) and is just a function. The service simply
stores all notifications; after the third one, the service displays all of the notifications by
window alert.
<pre>
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
</pre>
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
@@ -0,0 +1,78 @@
@ngdoc overview
@name Developer Guide: Angular Services: Injecting Services Into Controllers
@description
Using services as dependencies for controllers is very similar to using services as dependencies
for another service.
Since JavaScript is a dynamic language, DI can't figure out which services to inject by static
types (like in static typed languages). Therefore, you must specify the service name by using the
`$inject` property, which is an array containing strings with names of services to be injected.
The name must match the corresponding service ID registered with angular. The order of the service
IDs matters: the order of the services in the array will be used when calling the factory function
with injected parameters. The names of parameters in factory function don't matter, but by
convention they match the service IDs.
<pre>
function myController($loc, $log) {
this.firstMethod = function() {
// use $location service
$loc.setHash();
};
this.secondMethod = function() {
// use $log service
$log.info('...');
};
}
// which services to inject ?
myController.$inject = ['$location', '$log'];
</pre>
<doc:example>
<doc:source>
<script type="text/javascript">
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
function myController(notifyService) {
this.callNotify = function(msg) {
notifyService(msg);
};
}
myController.$inject = ['notify'];
</script>
<div ng:controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng:init="message='test'" type="text" ng:model="message" />
<button ng:click="callNotify(message);">NOTIFY</button>
</div>
</doc:source>
<doc:scenario>
it('should test service', function() {
expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
});
</doc:scenario>
</doc:example>
## Related Topics
{@link dev_guide.services.understanding_services Understanding Angular Services}
{@link dev_guide.services.creating_services Creating Angular Services}
{@link dev_guide.services.registering_services Registering Angular Services}
{@link dev_guide.services.managing_dependencies Managing Service Dependencies}
{@link dev_guide.services.testing_services Testing Angular Services}
## Related API
{@link api/angular.service Angular Service API}
@@ -0,0 +1,84 @@
@ngdoc overview
@name Developer Guide: Angular Services: Managing Service Dependencies
@description
Angular allows services to declare other services as dependencies needed for construction of their
instances.
To declare dependencies, you specify them in the factory function signature and via the `$inject`
property, as an array of string identifiers. Optionally the `$inject` property declaration can be
dropped (see "Inferring `$inject`" but note that that is currently an experimental feature).
Here is an example of two services that depend on each other, as well as on other services that are
provided by angular's web framework:
<pre>
/**
* batchLog service allows for messages to be queued in memory and flushed
* to the console.log every 50 seconds.
*
* @param {*} message Message to be logged.
*/
angular.service('batchLog', function($defer, $log) {
var messageQueue = [];
function log() {
if (messageQueue.length) {
$log('batchLog messages: ', messageQueue);
messageQueue = [];
}
$defer(log, 50000);
}
// start periodic checking
log();
return function(message) {
messageQueue.push(message);
}
}, {$inject: ['$defer', '$log']});
// note how we declared dependency on built-in $defer and $log services above
/**
* routeTemplateMonitor monitors each $route change and logs the current
* template via the batchLog service.
*/
angular.service('routeTemplateMonitor', function($route, batchLog) {
this.$on('$afterRouteChange', function() {
batchLog($route.current ? $route.current.template : null);
});
}, {$inject: ['$route', 'batchLog'], $eager: true});
</pre>
Things to notice in this example:
* The `batchLog` service depends on the built-in {@link api/angular.service.$defer $defer} and
{@link api/angular.service.$log $log} services, and allows messages to be logged into the
`console.log` in batches.
* The `routeTemplateMonitor` service depends on the built-in {@link api/angular.service.$route
$route} service as well as our custom `batchLog` service.
* The `routeTemplateMonitor` service is declared to be eager, so that it is started as soon as the
application starts.
* To underline the need for the eager instantiation of the `routeTemplateMonitor` service, nothing
else in the application depends on this service, and in this particular case the factory function
of this service doesn't return anything at all.
* Both of our services use the factory function signature as well as the `$inject` property to
declare their dependencies. It is important that the order of the string identifiers in the array
associated with the `$inject` property is the same as the order of argument names in the signature
of the factory function. Unless the dependencies are inferred from the function signature, it is
this array with IDs and their order that the injector uses to determine which services and in which
order to inject.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Services}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.injector Angular Injector API}
@@ -0,0 +1,22 @@
@ngdoc overview
@name Developer Guide: Angular Services
@description
Services are a feature that angular brings to client-side web apps from the server side, where
services have been commonly used for a long time. Services in angular apps are substitutable
objects that are wired together using {@link dev_guide.di dependency injection (DI)}. Services are
most often used with {@link dev_guide.di dependency injection}, also a key feature of angular apps.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
@@ -0,0 +1,69 @@
@ngdoc overview
@name Developer Guide: Angular Services: Registering Angular Services
@description
To register a service, register a factory function that creates the service with angular's
Injector. The Injector is exposed as {@link api/angular.scope.$service scope.$service}. The
following pseudo-code shows a simple service registration:
<pre>
angular.service('service id', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
</pre>
Note that you are not registering a service instance, but rather a factory function that will
create this instance when called.
# Instantiating Angular Services
A service can be instantiated eagerly or lazily. By default angular instantiates services lazily,
which means that a service will be created only when it is needed for instantiation of a service or
an application component that depends on it. In other words, angular won't instantiate lazy
services unless they are requested directly or indirectly by the application.
Eager services on the other hand, are instantiated right after the injector itself is created,
which happens when the angular {@link dev_guide.bootstrap application initializes}.
To override the default, you can request that a service is eagerly instantiated as follows:
<pre>
angular.service('service id', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
}, {$eager: true});
</pre>
While it is tempting to declare services as eager, only in few cases it is actually useful. If you
are unsure whether to make a service eager, it likely doesn't need to be. To be more specific, a
service should be declared as eager only if it fits one of these scenarios:
* Nothing in your application declares this service as its dependency, and this service affects the
state or configuration of the application (e.g. a service that configures `$route` or `$resource`
services)
* A guarantee is needed that the service will be instantiated at application boot time, usually
because the service passively observes the application and it is optional for other application
components to depend on it. An example of this scenario is a service that monitors and logs
application memory usage.
Lastly, it is important to realize that all angular services are applicaiton singletons. This means
that there is only one instance of a given service per injector. Since angular is lethally allergic
to the global state, it is possible to create multiple injectors, each with its own instance of a
given service, but that is rarely needed, except in tests where this property is crucially
important.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
@@ -0,0 +1,58 @@
@ngdoc overview
@name Developer Guide: Angular Services: Testing Angular Services
@description
Following is a unit test for the service in the example in {@link
dev_guide.services.creating_services Creating Angular Services}. The unit test example uses Jasmine
spy (mock) instead of a real browser alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
});
</pre>
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
## Related API
* {@link api/angular.service Angular Service API}
@@ -0,0 +1,37 @@
@ngdoc overview
@name Developer Guide: Angular Services: Understanding Angular Services
@description
Angular services are singletons that carry out specific tasks common to web apps, such as the
{@link api/angular.service.$xhr $xhr service} that provides low level access to the browser's
`XMLHttpRequest` object.
To use an angular service, you identify it as a dependency for the dependent (a controller, or
another service) that depends on the service. Angular's dependency injection subsystem takes care
of the rest. The angular injector subsystem is in charge of service instantiation, resolution of
dependencies, and provision of dependencies to factory functions as requested.
Angular injects dependencies using "constructor" injection (the service is passed in via a factory
function). Because JavaScript is a dynamically typed language, angular's dependency injection
subsystem cannot use static types to identify service dependencies. For this reason a dependent
must explicitly define its dependencies by using the `$inject` property. For example:
myController.$inject = ['$location'];
The angular web framework provides a set of services for common operations. Like other core angular
variables and identifiers, the built-in services always start with `$` (such as `$xhr` mentioned
above). You can also create your own custom services.
## Related Topics
* {@link dev_guide.di About Angular Dependency Injection}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.injector Injector API}
@@ -0,0 +1,34 @@
@ngdoc overview
@name Developer Guide: Templates: Working With CSS in Angular
@description
Angular sets these CSS classes. It is up to your application to provide useful styling.
# CSS classes used by angular
* `ng-invalid`, `ng-valid`
- **Usage:** angular applies this class to an input widget element if that element's input does
notpass validation. (see {@link api/angular.widget.input input} widget).
* `ng-pristine`, `ng-dirty`
- **Usage:** angular {@link api/angular.widget.input input} widget applies `ng-pristine` class
to a new input widget element which did not have user interaction. Once the user interacts with
the input widget the class is changed to `ng-dirty`.
# Marking CSS classes
* `ng-widget`, `ng-directive`
- **Usage:** angular sets these class on elements where {@link api/angular.widget widget} or
{@link api/angular.directive directive} has bound to.
* Old browser support
- Pre v9, IE browsers could not select `ng:include` elements in CSS, because of the `:`
character. For this reason angular also sets `ng-include` class on any element which has `:`
character in the name by replacing `:` with `-`.
## Related Topics
* {@link dev_guide.templates Angular Templates}
* {@link dev_guide.forms Angular Forms}
@@ -0,0 +1,38 @@
@ngdoc overview
@name Developer Guide: Templates: Data Binding in Angular
@description
Data-binding in angular web apps is the automatic syncing of data between the model and view
components. The way that angular implements data-binding lets you treat the model as the
single-source-of-truth in your application. The view is a projection of the model at all times.
When the model changes, the view reflects the change, and vice versa.
## Data Binding in Classical Template Systems
<img class="right" src="img/One_Way_Data_Binding.png"/>
Most templating systems bind data in only one direction: they merge template and model components
together into a view, as illustrated in the diagram. After the merge occurs, changes to the model
or related sections of the view are NOT automatically reflected in the view. Worse, any changes
that the user makes to the view are not reflected in the model. This means that the developer has
to write code that constantly syncs the view with the model and the model with the view.
## Data Binding in Angular Templates
<img class="right" src="img/Two_Way_Data_Binding.png"/>
The way angular templates works is different, as illustrated in the diagram. They are different
because first the template (which is the uncompiled HTML along with any additional markup or
directives) is compiled on the browser, and second, the compilation step produces a live view. We
say live because any changes to the view are immediately reflected in the model, and any changes in
the model are propagated to the view. This makes the model always the single-source-of-truth for
the application state, greatly simplifying the programing model for the developer. You can think of
the view as simply an instant projection of your model.
Because the view is just a projection of the model, the controller is completely separated from the
view and unaware of it. This makes testing a snap because it is easy to test your controller in
isolation without the view and the related DOM/browser dependency.
## Related Topics
* {@link dev_guide.scopes Angular Scopes}
* {@link dev_guide.templates Angular Templates}
@@ -0,0 +1,68 @@
@ngdoc overview
@name Developer Guide: Templates: Filters: Creating Angular Filters
@description
Writing your own filter is very easy: just define a JavaScript function on the `angular.filter`
object.
The framework passes in the input value as the first argument to your function. Any filter
arguments are passed in as additional function arguments.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. The `$element` variable allows the
filter to manipulate the DOM.
The following sample filter reverses a text string. In addition, it conditionally makes the
text upper-case and assigns color.
<doc:example>
<doc:source>
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
// conditional based on optional argument
if (uppercase) {
out = out.toUpperCase();
}
// DOM manipulation using $element
if (color) {
this.$element.css('color', color);
}
return out;
});
function Ctrl() {
this.greeting = 'hello';
}
</script>
<div ng:controller="Ctrl">
<input ng:model="greeting" type="greeting"><br>
No filter: {{greeting}}<br>
Reverse: {{greeting|reverse}}<br>
Reverse + uppercase: {{greeting|reverse:true}}<br>
Reverse + uppercase + blue: {{greeting|reverse:true:"blue"}}
</div>
</doc:source>
<doc:scenario>
it('should reverse greeting', function() {
expect(binding('greeting|reverse')).toEqual('olleh');
input('greeting').enter('ABC');
expect(binding('greeting|reverse')).toEqual('CBA');
});
</doc:scenario>
</doc:example>
## Related Topics
* {@link dev_guide.templates.filters Understanding Angular Filters}
* {@link dev_guide.compiler Angular HTML Compiler}
## Related API
* {@link api/angular.filter Angular Filter API}
@@ -0,0 +1,27 @@
@ngdoc overview
@name Developer Guide: Templates: Understanding Angular Filters
@description
Angular filters format data for display to the user. In addition to formatting data, filters can
also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
to filtered output.
For example, you might have a data object that needs to be formatted according to the locale before
displaying it to the user. You can pass expressions through a chain of filters like this:
name | uppercase
The expression evaluator simply passes the value of name to `angular.filter.uppercase()`.
In addition to formatting data, filters can also modify the DOM. This allows filters to handle
tasks such as conditionally applying CSS styles to filtered output.
## Related Topics
* {@link dev_guide.templates.filters.using_filters Using Angular Filters}
* {@link dev_guide.templates.filters.creating_filters Creating Angular Filters}
## Related API
* {@link api/angular.filter Angular Filter API}
@@ -0,0 +1,40 @@
@ngdoc overview
@name Developer Guide: Templates: Filters: Using Angular Filters
@description
Filters can be part of any {@link api/angular.scope} evaluation but are typically used to format
expressions in bindings in your templates:
{{ expression | filter }}
Filters typically transform the data to a new data type, formatting the data in the process.
Filters can also be chained, and can take optional arguments.
You can chain filters using this syntax:
{{ expression | filter1 | filter2 }}
You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
2 decimal points:
123 | number:2
Here are some examples that show values before and after applying different filters to an
expression in a binding:
* No filter: `{{1234.5678}}` => `1234.5678`
* Number filter: `{{1234.5678|number}}` => `1,234.57`. Notice the "," and rounding to two
significant digits.
* Filter with arguments: `{{1234.5678|number:5}}` => `1,234.56780`. Filters can take optional
arguments, separated by colons in a binding. For example, the "number" filter takes a number
argument that specifies how many digits to display to the right of the decimal point.
## Related Topics
* {@link dev_guide.templates.filters Understanding Angular Filters}
* {@link dev_guide.templates.filters.creating_filters Creating Angular Filters}
## Related API
* {@link api/angular.filter Angular Filter API}
@@ -0,0 +1,59 @@
@ngdoc overview
@name Developer Guide: Understanding Angular Templates
@description
An angular template is the declarative specification that, along with information from the model
and controller, becomes the rendered view that a user sees in the browser. It is the static DOM,
containing HTML, CSS, and angular-specific elements and angular-specific element attributes. The
angular elements and attributes direct angular to add behavior and transform the template DOM into
the dynamic view DOM.
These are the types of angular elements and element attributes you can use in a template:
* {@link dev_guide.compiler.directives Directive} — An attribute that augments an existing DOM
element.
* {@link dev_guide.compiler.widgets Widget} — A custom DOM element. An example of a built-in widget
is {@link api/angular.widget.@ng:repeat ng:repeat}.
* {@link dev_guide.compiler.markup Markup} — Shorthand for a widget or a directive. The double
curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.
* {@link dev_guide.templates.filters Filter} — Formats your data for display to the user.
* {@link dev_guide.forms Form widgets} — Lets you validate user input.
Note: In addition to declaring the elements above in templates, you can also access these elements
in JavaScript code.
The following code snippet shows a simple angular template made up of standard HTML tags along with
angular {@link dev_guide.compiler.directives directives}, {@link dev_guide.compiler.markup markup},
and {@link dev_guide.expressions expressions}:
<pre>
<html>
<!-- Body tag augmented with ng:controller directive -->
<body ng:controller="MyController">
<input ng:model="foo" value="bar">
<!-- Button tag with ng:click directive, and
string expression 'buttonText'
wrapped in "{{ }}" markup -->
<button ng:click="changeFoo()">{{buttonText}}</button>
<script src="angular.js" ng:autobind>
</body>
</html>
</pre>
In a simple single-page app, the template consists of HTML, CSS, and angular directives contained
in just one HTML file (usually `index.html`). In a more complex app, you can display multiple views
within one main page using "partials", which are segments of template located in separate HTML
files. You "include" the partials in the main page using the {@link api/angular.service.$route
$route} service in conjunction with the {@link api/angular.widget.ng:view ng:view} directive. An
example of this technique is shown in the {@link tutorial/ angular tutorial}, in steps seven and
eight.
## Related Topics
* {@link dev_guide.templates.filters Angular Filters}
* {@link dev_guide.forms Angular Forms}
## Related API
* {@link api/index API Reference}
@@ -0,0 +1,279 @@
@ngdoc overview
@name Developer Guide: Unit Testing
@description
JavaScript is a dynamically typed language which comes with great power of expression, but it also
come with almost no-help from the compiler. For this reason we feel very strongly that any code
written in JavaScript needs to come with a strong set of tests. We have built many features into
angular which makes testing your angular applications easy. So there is no excuse for not do it.
# It is all about NOT mixing concerns
Unit testing as the name implies is about testing individual units of code. Unit tests try to
answer the question: Did I think about the logic correctly. Does the sort function order the list
in the right order. In order to answer such question it is very important that we can isolate it.
That is because when we are testing the sort function we don't want to be forced into crating
related pieces such as the DOM elements, or making any XHR calls in getting the data to sort. While
this may seem obvious it usually is very difficult to be able to call an individual function on a
typical project. The reason is that the developers often time mix concerns, and they end up with a
piece of code which does everything. It reads the data from XHR, it sorts it and then it
manipulates the DOM. With angular we try to make it easy for you to do the right thing, and so we
provide dependency injection for your XHR (which you can mock out) and we crated abstraction which
allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
it is easy to write a sort function which sorts some data, so that your test can create a data set,
apply the function, and assert that the resulting model is in the correct order. The test does not
have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
DOM in the right way. Angular is written with testability in mind, but it still requires that you
do the right thing. We tried to make the right thing easy, but angular is not magic, which means if
you don't follow these, you may very well end up with an untestable application.
## Dependency Inject
There are several ways in which you can get a hold of a dependency:
1. You could create it using the `new` operator.
2. You could look for it in a well know place, also known as global singleton.
3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
the registry? Must likely by looking it up in a well know place. See #2)
4. You could expect that the it be handed to you.
Out of the list above only the last of is testable. Lets look at why:
### Using the `new` operator
While there is nothing wrong with the `new` operator fundamentally the issue is that calling a new
on a constructor permanently binds the call site to the type. For example lets say that we are
trying to instantiate an `XHR` so that we can get some data from the server.
<pre>
function MyClass() {
this.doWork = function() {
var xhr = new XHR();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {...}
xhr.send();
}
}
</pre>
The issue becomes, that in tests, we would very much like to instantiate a `MockXHR` which would
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
permanently bound to the actual one, and there is no good way to replace it. Yes there is monkey
patching, that is a bad idea for many reasons, which is outside the scope of this document.
The class above is hard to test since we have to resort to monkey patching:
<pre>
var oldXHR = XHR;
XHR = function MockXHR() {};
var myClass = new MyClass();
myClass.doWork();
// assert that MockXHR got called with the right arguments
XHR = oldXHR; // if you forget this bad things will happen
</pre>
### Global look-up:
Another way to approach the problem is look for the service in a well known location.
<pre>
function MyClass() {
this.doWork = function() {
global.xhr({
method:'...',
url:'...',
complete:function(response){ ... }
})
}
}
</pre>
While no new instance of dependency is being created, it is fundamentally the same as `new`, in
that there is no good way to intercept the call to `global.xhr` for testing purposes, other then
through monkey patching. The basic issue for testing is that global variable needs to be mutated in
order to replace it with call to a mock method. For further explanation why this is bad see: {@link
http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/ Brittle Global
State & Singletons}
The class above is hard to test since we have to change global state:
<pre>
var oldXHR = glabal.xhr;
glabal.xhr = function mockXHR() {};
var myClass = new MyClass();
myClass.doWork();
// assert that mockXHR got called with the right arguments
global.xhr = oldXHR; // if you forget this bad things will happen
</pre>
### Service Registry:
It may seem as that this can be solved by having a registry for all of the services, and then
having the tests replace the services as needed.
<pre>
function MyClass() {
var serviceRegistry = ????;
this.doWork = function() {
var xhr = serviceRegistry.get('xhr');
xhr({
method:'...',
url:'...',
complete:function(response){ ... }
})
}
</pre>
However, where dose the serviceRegistry come from? if it is:
* `new`-ed up, the the test has no chance to reset the services for testing
* global look-up, then the service returned is global as well (but resetting is easier, since
there is only one global variable to be reset).
The class above is hard to test since we have to change global state:
<pre>
var oldServiceLocator = glabal.serviceLocator;
glabal.serviceLocator.set('xhr', function mockXHR() {});
var myClass = new MyClass();
myClass.doWork();
// assert that mockXHR got called with the right arguments
glabal.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
</pre>
### Passing in Dependencies:
Lastly the dependency can be passed in.
<pre>
function MyClass(xhr) {
this.doWork = function() {
xhr({
method:'...',
url:'...',
complete:function(response){ ... }
})
}
</pre>
This is the preferred way since the code makes no assumptions as to where the `xhr` comes from,
rather that whoever created the class was responsible for passing it in. Since the creator of the
class should be different code than the user of the class, it separates the responsibility of
creation from the logic, and that is what dependency-injection is in a nutshell.
The class above is very testable, since in the test we can write:
<pre>
function xhrMock(args) {...}
var myClass = new MyClass(xhrMock);
myClass.doWork();
// assert that xhrMock got called with the right arguments
</pre>
Notice that no global variables were harmed in the writing of this test.
Angular comes with {@link dev_guide.di dependency-injection} built in which makes the right thing
easy to do, but you still need to do it if you wish to take advantage of the testability story.
## Controllers
What makes each application unique is its logic, which is what we would like to test. If the logic
for your application is mixed in with DOM manipulation, it will be hard to test as in the example
below:
<pre>
function PasswordController() {
// get references to DOM elements
var msg = $('.ex1 span');
var input = $('.ex1 input');
var strength;
this.grade = function() {
msg.removeClass(strength);
var pwd = input.val();
password.text(pwd);
if (pwd.length > 8) {
strength = 'strong';
} else if (pwd.length > 3) {
strength = 'medium';
} else {
strength = 'weak';
}
msg
.addClass(strength)
.text(strength);
}
}
</pre>
The code above is problematic from testability, since it requires your test to have the right kind
of DOM present when the code executes. The test would look like this:
<pre>
var input = $('<input type="text"/>');
var span = $('<span>');
$('body').html('<div class="ex1">')
.find('div')
.append(input)
.append(span);
var pc = new PasswordController();
input.val('abc');
pc.grade();
expect(span.text()).toEqual('weak');
$('body').html('');
</pre>
In angular the controllers are strictly separated from the DOM manipulation logic which results in
a much easier testability story as can be seen in this example:
<pre>
function PasswordCntrl() {
this.password = '';
this.grade = function() {
var size = this.password.length;
if (size > 8) {
this.strength = 'strong';
} else if (size > 3) {
this.strength = 'medium';
} else {
this.strength = 'weak';
}
};
}
</pre>
and the tests is straight forward
<pre>
var pc = new PasswordController();
pc.password('abc');
pc.grade();
expect(span.strength).toEqual('weak');
</pre>
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
that such a test tells a story, rather then asserting random bits which don't seem to be related.
## Filters
{@link api/angular.filter Filters} are functions which transform the data into user readable
format. They are important because they remove the formatting responsibility from the application
logic, further simplifying the application logic.
<pre>
angular.filter('length', function(text){
return (''+(text||'')).length;
});
var length = angular.filter('length');
expect(length(null)).toEqual(0);
expect(length('abc')).toEqual(3);
</pre>
## Directives
Directives in angular are responsible for updating the DOM when the state of the model changes.
## Mocks
oue
## Global State Isolation
oue
# Preferred way of Testing
uo
## JavaScriptTestDriver
ou
## Jasmine
ou
## Sample project
uoe
+57
View File
@@ -0,0 +1,57 @@
@ngdoc overview
@name Developer Guide
@description
Welcome to the angular Developer Guide. If you are here to learn the details of how to use angular
to develop web apps, you've come to the right place.
If you are completely or relatively unfamiliar with angular, you may want to check out one or both
of the following documents before returning here to the Developer Guide:
* {@link misc/started Getting Started}
* {@link tutorial/index Angular Tutorial}
<hr>
## {@link dev_guide.overview Overview of Angular}
## {@link dev_guide.bootstrap Initializing Angular}
* {@link dev_guide.bootstrap.auto_bootstrap Understanding Automatic Initialization}
* {@link dev_guide.bootstrap.manual_bootstrap Understanding Manual Initialization}
## {@link dev_guide.mvc About MVC in Angular}
* {@link dev_guide.mvc.understanding_model Understanding the Model Component}
* {@link dev_guide.mvc.understanding_controller Understanding the Controller Component}
* {@link dev_guide.mvc.understanding_view Understanding the View Component}
## {@link dev_guide.scopes Angular Scope Objects}
* {@link dev_guide.scopes.understanding_scopes Understanding Angular Scope Objects}
* {@link dev_guide.scopes.internals Angular Scope Internals}
## {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.compiler.directives Understanding Angular Directives}
* {@link dev_guide.compiler.widgets Understanding Angular Widgets}
* {@link dev_guide.compiler.directives_widgets Comparing Directives and Widgets}
* {@link dev_guide.compiler.markup Understanding Angular Markup}
## {@link dev_guide.templates Angular Templates}
* {@link dev_guide.templates.filters Understanding Angular Filters}
* {@link dev_guide.forms Understanding Angular Forms}
## {@link dev_guide.services Angular Services}
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.testing_services Testing Angular Services}
## {@link dev_guide.di About Dependency Injection}
* {@link dev_guide.di.understanding_di Understanding DI in Angular}
* {@link dev_guide.di.using_di_controllers Using DI in Controllers}
+262
View File
@@ -0,0 +1,262 @@
@ngdoc overview
@name Contributing
@description
* <a href="#H1_1">License</a>
* <a href="#H1_2">Contributing to Source Code</a>
* <a href="#H1_3">Applying Code Standards</a>
* <a href="#H1_4">Checking Out and Building `Angular`</a>
* <a href="#H1_5">Submitting Your Changes</a>
<a name="H1_1"></a>
# License
`Angular` is an open source project licensed under the {@link
http://github.com/angular/angular.js/blob/master/LICENSE MIT license}. Your contributions are
always welcome. When working with `angular` source base, please follow the guidelines provided on
this page.
<a name="H1_2"></a>
# Contributing to Source Code
We'd love for you to contribute to our source code and to make `angular` even better than it is
today! Here are the guidelines we'd like you to use:
* Major changes that you intend to contribute to the project must be discussed first on our {@link
https://groups.google.com/forum/?hl=en#!forum/angular mailing list} so that we can better
coordinate our efforts, prevent duplication of work, and help you to craft the change so that it
is successfully accepted upstream.
* Small changes and bug fixes can be crafted and submitted to Github as a <a href="#H1_5">pull
request</a>.
<a name="H1_3"></a>
# Applying Code Standards
To ensure consistency throughout the source code, keep these rules in mind as you are working:
* All features or bug fixes must be tested by one or more <a href="#unit-tests">specs</a>.
* All public API methods must be documented with ngdoc, an extended version of jsdoc (we added
support for markdown and templating via `@ngdoc` tag). To see how we document our APIs, please
check out the existing ngdocs.
* With the exceptions listed below, we follow the rules contained in {@link
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml Google's JavaScript Style
Guide}:
* Do not use namespaces: Instead, we wrap the entire `angular` code base in an anonymous closure
and export our API explicitly rather than implicitly.
* Wrap all code at 100 characters.
* Instead of complex inheritance hierarchies, we prefer simple objects. We use prototypical
inheritance only when absolutely necessary.
* We love functions and closures and, whenever possible, prefer them over objects.
* To write concise code that can be better minified, internally we use aliases that map to the
external API. See our existing code to see what we mean.
* We don't go crazy with type annotations for private internal APIs unless it's an internal API
that is used throughout `angular`. The best guidance is to do what makes the most sense.
<a name="H1_4"></a>
# Checking Out and Building Angular
The `angular` source code is hosted at {@link http://github.com Github}, which we also use to
accept code contributions. Several steps are needed to check out and build `angular`:
## Installation Dependencies
Before you can build `angular`, you must install or configure the following dependencies on your
machine:
* {@link http://rake.rubyforge.org Rake}: We use Rake as our build system, which is pre-installed
on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
Rake website.
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation and to run a
development web server. Depending on your system, you can install Node either from source or as a
pre-packaged bundle.
You'll also need npm and the following npm modules:
* install npm: `curl http://npmjs.org/install.sh | sh`
* install q: `npm install q`
* install qq: `npm install qq`
* install q-fs: `npm install q-fs`
* install jasmine-node: `npm install jasmine`
* Java: The Java runtime is used to run {@link http://code.google.com/p/js-test-driver
JsTestDriver} (JSTD), which we use to run our unit test suite. JSTD binaries are part of the
`angular` source base, which means there is no need to install or configure it separately.
* Git: The {@link http://help.github.com/mac-git-installation Github Guide to Installing Git} is
quite a good source for information on Git.
## Creating a Github Account and Forking Angular
To create a Github account, follow the instructions {@link https://github.com/signup/free here}.
Afterwards, go ahead and {@link http://help.github.com/forking fork} the {@link
https://github.com/angular/angular.js main angular repository}.
## Building `Angular`
To build `angular`, you check out the source code and use Rake to generate the non-minified and
minified `angular` files:
1. To clone your Github repository, run:
git clone git@github.com:<github username>/angular.js.git
2. To go to the `angular` directory, run:
cd angular.js
3. To add the main `angular` repository as an upstream remote to your repository, run:
git remote add upstream https://github.com/angular/angular.js.git
4. To build `angular`, run:
rake package
The build output can be located under the `build` directory. It consists of the following files and
directories:
* `angular-<version>.tgz` — This is the complete tarball, which contains all of the release build
artifacts.
* `angular.js` — The non-minified `angular` script.
* `angular.min.js` — The minified `angular` script.
* `angular-scenario.js` — The `angular` End2End test runner.
* `angular-ie-compat.js` — The Internet Explorer compatibility patch file.
* `docs/` — A directory that contains all of the files needed to run `docs.angularjs.org`.
* `docs/index.html` — The main page for the documentation.
* `docs/docs-scenario.html` — The End2End test runner for the documentation application.
## Running a Local Development Web Server
To debug or test code, it is often useful to have a local HTTP server. For this purpose, we have
made available a local web server based on Node.js.
1. To start the web server, run:
./nodeserver.sh
2. To access the local server, go to this website:
http://localhost:8000/
By default, it serves the contents of the `angular` project directory.
<a name="unit-tests"></a>
## Running the Unit Test Suite
Our unit and integration tests are written with Jasmine and executed with JsTestDriver. To run the
tests:
1. To start the JSTD server, run:
./server.sh
2. To capture one or more browsers, go to this website:
http://localhost:9876/
3. To trigger a test execution, run:
./test.sh
4. To automatically run the test suite each time one or more of the files in the project directory
is changed, you can install `watchr` and then run:
watchr watchr.rb
5. To view the output of each test run, you can tail this log file:
./logs/jstd.log
## Running the End2End Test Suite
To run the End2End test suite:
1. Start the local web server.
2. In a browser, go to:
http://localhost:8000/build/docs/docs-scenario.html
The tests are executed automatically.
<a name="H1_5"></a>
# Submitting Your Changes
To create and submit a change:
1. Create a new branch off the master for your changes:
git branch my-fix-branch
2. Check out the branch:
git checkout my-fix-branch
3. Create your patch, make sure to have plenty of tests (that pass).
4. Commit your changes:
git commit -a
5. Run JavaScript Lint and be sure to address all new warnings and errors:
rake lint
6. Push your branch to Github:
git push origin my-fix-branch
7. In Github, send a pull request to `angular:master`.
8. When the patch is reviewed and merged, delete your branch and pull yours — and other — changes
from the main (upstream) repository:
1. To delete the branch in Github, run:
git push origin :my-fix-branch
2. To check out the master branch, run:
git checkout master
3. To delete a local branch, run:
git branch -D my-fix-branch
4. To update your master with the latest upstream version, run:
git pull --ff upstream master
That's it! Thank you for your contribution!
+70
View File
@@ -0,0 +1,70 @@
@ngdoc overview
@name Downloading
@description
# Including angular scripts from the angular server
The quickest way to get started is to point your html `<script>` tag to a
<http://code.angularjs.org/> URL. This way, you don't have to download anything or maintain a
local copy.
There are two types of angular script URLs you can point to, one for development and one for
production:
* __angular-<version>.js__ — This is the human-readable, non-minified version, suitable for web
development.
* __angular-<version>.min.js__ — This is the minified version, which we strongly suggest you use in
production.
To point your code to an angular script on the angular server, use the following template. This
example points to (non-minified) version 0.9.12:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
<title>My Angular App</title>
<script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
</head>
<body>
</body>
</html>
</pre>
# Downloading and hosting angular files locally
This option is for those who want to work with angular offline, or those who want to host the
angular files on their own servers.
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all of the
angular versions since we started releasing versioned build artifacts (quite late in the project
lifetime). Each directory contains all artifacts that we released for a particular version.
Download the version you want and have fun.
Each directory under <http://code.angularjs.org/> includes the following set of files:
* __`angular-<version>.js`__ — This file is non-obfuscated, non-minified, and human-readable by
opening it it any editor or browser. In order to get better error messages during development, you
should always use this non-minified angular script.
* __`angular-<version>.min.js`__ — This is a minified and obfuscated version of
`angular-<version>.js` created with the Closure compiler. Use this version for production in order
to minimize the size of the application that is downloaded by your user's browser.
* __`angular-<version>.tgz`__ — This is a tarball archive that contains all of the files released
for this angular version. Use this file to get everything in a single download.
* __`angular-ie-compat-<version>.js`__ — This is a special file that contains code and data
specifically tailored for getting Internet Explorer to work with angular. If you host your own copy
of angular files, make sure that this file is available for download, and that it resides under the
same parent path as `angular-<version>.js` or `angular-<version>.min.js`.
* __`angular-mocks-<version>.js`__ — This file contains an implementation of mocks that makes
testing angular apps even easier. Your unit/integration test harness should load this file after
`angular-<version>.js` is loaded.
* __`angular-scenario-<version>.js`__ — This file is a very nifty JavaScript file that allows you
to write and execute end-to-end tests for angular applications.
* __`docs-<version>`__ — this directory contains all the files that compose the
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
our docs, or even more importantly, view the docs offline.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name FAQ
@description
@@ -29,7 +28,7 @@ attacks. angular does round-trip escaping on all strings for you.
### Can I download the source, build, and host the angular environment locally?
Yes. See instructions in {@link guide.downloading downloading}.
Yes. See instructions in {@link downloading}.
### Is angular a templating system?
@@ -63,18 +62,19 @@ in angular.
Yes, angular uses {@link http://jquery.com/ jQuery}, the open source DOM manipulation library.
If jQuery is not present in your script path, angular falls back on its own implementation of
{@link angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
{@link api/angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
manipulate the DOM.
### What is testability like in angular?
Very testable. It has an integrated dependency injection framework. See
{@link angular.service service} for details.
{@link api/angular.service service} for details.
### How can I learn more about angular?
Watch the July 28, 2010 talk
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building AJAX Apps}".
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building
AJAX Apps}".
### How is angular licensed?
+145
View File
@@ -0,0 +1,145 @@
@ngdoc overview
@name Getting Started
@description
# Hello World!
A great way for you to get started with AngularJS is to create the tradtional
"Hello World!" app:
1. In your favorite text editor, create an HTML file
(for example, `helloworld.html`).
2. From the __Source__ box below, copy and paste the code into your HTML file.
(Double-click on the source to easily select all.)
3. Open the file in your web browser.
<doc:example>
<doc:source>
Hello {{'World'}}!
</doc:source>
</doc:example>
The resulting web page should look something like the following:
<img class="center" src="img/helloworld.png" border="1" />
Now let's take a closer look at that code, and see what is going on behind
the scenes.
The first line of interest defines the `ng` namespace, which makes
AngularJS work across all browsers (especially important for IE):
<pre>
<html xmlns:ng="http://angularjs.org">
</pre>
The next line downloads the angular script, and instructs angular to process
the entire HTML page when it is loaded:
<pre>
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js"
ng:autobind></script>
</pre>
(For details on what happens when angular processes an HTML page,
see {@link guide/dev_guide.bootstrap Bootstrap}.)
Finally, this line in the `<body>` of the page is the template that describes
how to display our greeting in the UI:
<pre>
Hello {{'World'}}!
</pre>
Note the use of the double curly brace markup (`{{ }}`) to bind the expression to
the greeting text. Here the expression is the string literal 'World'.
Next let's look at a more interesting example, that uses AngularJS to
bind a dynamic expression to our greeting text.
# Hello AngularJS World!
This example demonstrates angular's two-way data binding:
1. Edit the HTML file you created in the "Hello World!" example above.
2. Replace the contents of `<body>` with the code from the __Source__ box below.
3. Refresh your browser window.
<doc:example>
<doc:source>
Your name: <input type="text" ng:model="yourname" value="World"/>
<hr/>
Hello {{yourname}}!
</doc:source>
</doc:example>
After the refresh, the page should look something like this:
<img class="left" src="img/helloworld_2way.png" border="1" />
These are some of the important points to note from this example:
* The text input {@link api/angular.widget widget} called `yourname` is bound to a model variable
called `yourname`.
* The double curly braces notation binds the `yourname` model to the greeting text.
* You did not need to explicitly register an event listener or define an event handler for events!
Now try typing your name into the input box, and notice the immediate change to
the displayed greeting. This demonstrates the concept of angular's
{@link guide/dev_guide.templates.databinding bi-directional data binding}. Any changes to the input
field are immediately
reflected in the model (one direction), and any changes to the model are
reflected in the greeting text (the other direction).
# Anatomy Of An Angular App
This section describes the 3 parts of an angular app, and explains how they map to the
Model-View-Controller design pattern:
## Templates
Templates, which you write in HTML and CSS, serve as the View. You add elements, attributes, and
markup to HTML, which serve as instructions to the angular compiler. The angular compiler is fully
extensible, meaning that with angular you can build your own declarative language on top of HTML!
## Application Logic and Behavior
Application Logic and Behavior, which you define in JavaScript, serve as the Controller. With
angular (unlike with standard AJAX applications) you don't need to write additional listeners or
DOM manipulators, because they are built-in. This feature makes your application logic very easy to
write, test, maintain, and understand.
## Data
The Model is referenced from properties on {@link guide/dev_guide.scopes angular scope objects}.
The data in your model could be Javascript objects, arrays, or primitives, it doesn't matter. What
matters is that these are all referenced by the scope object.
Angular employs scopes to keep your data model and your UI in sync. Whenever something occurs to
change the state of the model, angular immediately reflects that change in the UI, and vice versa.
The following illustration shows the parts of an angular application and how they work together:
<img class="left" src="img/angular_parts.png" border="0" />
In addition, angular comes with a set of Services, which have the following properties:
* The services provided are very useful for building web applications.
* You can extend and add application-specific behavior to services.
* Services include Dependency-Injection, XHR, caching, URL routing, and browser abstraction.
# Where To Go Next
* If you like what you've learned so far, you should definitely check out our awesome {@link
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}.
* For additional hands-on examples of using AngularJS, including more source code that you can
copy and paste into your own pages, take a look through the {@link cookbook/ Cookbook}.
+150
View File
@@ -0,0 +1,150 @@
@ngdoc overview
@name Tutorial
@description
A great way to get introduced to Angular is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
of Android devices, lets you filter the list to see only devices that interest you, and then view
details for any device.
<img src="img/tutorial/catalog_screen.png">
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
or plug-ins. As you work through the tutorial, you will:
* See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.
* See how Angular creates listeners on your data without the need for DOM manipulation.
* Learn a better, easier way to test your web apps.
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.
And all of this works in any browser without modification to the browser!
When you finish the tutorial you will be able to:
* Create a dynamic application that works in any browser.
* Define the differences between Angular and common JavaScript frameworks.
* Understand how data binding works in AngularJS.
* Use the angular-seed project to quickly boot-strap your own projects.
* Create and run tests.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
writing and running unit and end-to-end tests. Experiments at the end of each step provide
suggestions for you learn more about AngularJS and the application you are building.
You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
{@link misc/started Getting Started} document.
# Working with the code
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. Options for working with the tutorial are to use the Git versioning system for source
code management or to use scripts that copy snapshots of project files into your workspace
(`sandbox`) directory. Select one of the tabs below and follow the instructions for setting up your
computer for your preferred option.
<doc:tutorial-instructions show="true">
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed by running the
following command in a terminal window:</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Download Git from the <a href="http://git-scm.com/download">Git</a> site.</p>
<p>You can build Git from source or use the pre-compiled package.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This command creates the <code>angular-phonecat</code> directory in your current
directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p></li>
<li><p>You will need an http server running on your system. Mac and Linux machines typically
have Apache pre-installed, but If you don't already have one installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a>. Use <code>node</code> to run <code>scripts/web-server.js</code>, a simple bundled
http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Java to run unit tests, so run the following command to verify that you
have <a href="http://java.com/">Java</a> installed and that the <code>java</code> executable is on
your <code>PATH</code>.</p>
<pre><code>java -version</code></pre>
<p></p></li>
<li><p>Install msysGit from <a href="http://git-scm.com/download">the Git</a> site.</p></li>
<li><p>Open msysGit bash and clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This command creates the angular-phonecat directory in your current directory.</p></li>
<li><p>Change your current directory to angular-phonecat.</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p>
<p>You should run all <code>git</code> commands from msysGit bash.</p>
<p>Other commands like <code>test-server.bat</code> or <code>test.bat</code> should be
executed from the Windows command line.</li>
<li><p>You need an http server running on your system. If you don't already have one
installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple, bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>You need Java to run unit tests, so verify that you have <a
href="http://java.com/">Java</a> installed by running the following command in a terminal
window:</p>
<pre><code>java -version</code></pre>
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
containing all of the files and unzip them into the [tutorial-dir] directory</p>.</li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from your
<code>sandbox</code> directory.</p></li>
<li><p>You need an http server running on your system and Mac and Linux machines typically
have Apache pre-installed. If you don't have an http server installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a> and use it to run <code>scripts/web-server.js</code>, a simple bundled http
server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed and that the
<code>java</code> executable is on your <code>PATH</code> by running the following command in the
Windows command line:</p>
<pre><code>java -version</code></pre>
<p>You need Java to run unit tests, so download the <a
href="http://code.angularjs.org/angular-phonecat/">zip archive</a> that contains all of the files
and unzip the files into the [tutorial-dir] directory</p></li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
<li><p>You need an http server running on your system, but if you don't already have one
already installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
</doc:tutorial-instructions>
The last thing to do is to make sure your computer has a web browser and a good text editor
installed. Now, let's get going with {@link step_00 step 0}.
+216
View File
@@ -0,0 +1,216 @@
@ngdoc overview
@name Tutorial: 0 - angular-seed
@description
<ul doc:tutorial-nav="0"></ul>
You are now ready to build the Angular phonecat application. In this step, you will become familiar
with the most important source code files, learn how to start the development servers bundled with
angular-seed, and run the application in the browser.
<doc:tutorial-instructions show="true">
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>In angular-phonecat directory, run this command:</p>
<pre><code>git checkout -f step-0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run
<code>./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="git-win" title="Git on Windows">
<ol>
<li><p>Open msysGit bash and run this command (in angular-phonecat directory):</p>
<pre><code>git checkout -f step-0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node
scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>In the angular-phonecat directory, run this command:</p>
<pre><code>./goto_step.sh 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run
<code>./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the angular-phonecat
<code>sandbox</code> directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Open windows command line and run this command (in the angular-phonecat directory):</p>
<pre><code>goto_step.bat 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node
scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the angular-phonecat
<code>sandbox</code> directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
</doc:tutorial-instructions>
You can now see the page in your browser. It's not very exciting, but that's OK.
The static HTML page that displays "Nothing here yet!" was constructed with the HTML code shown
below. The code contains some key Angular elements that we will need going forward.
__`app/index.html`:__
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org/">
<head>
<meta charset="utf-8">
<title>my angular app</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
Nothing here yet!
<script src="lib/angular/angular.js" ng:autobind></script>
</body>
</html>
</pre>
## What is the code doing?
* xmlns declaration
<html xmlns:ng="http://angularjs.org">
This `xmlns` declaration for the `ng` namespace must be specified in all Angular applications in
order to make Angular work with XHTML and IE versions older than 9 (regardless of whether you are
using XHTML or HTML).
* Angular script tag
<script src="lib/angular/angular.js" ng:autobind>
This single line of code is all that is needed to bootstrap an angular application.
The code downloads the `angular.js` script and registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
looks for the {@link api/angular.directive.ng:autobind ng:autobind} attribute. If Angular finds
`ng:autobind`, it creates a root scope for the application and associates it with the `<html>`
element of the template:
<img src="img/tutorial/tutorial_00_final.png">
As you will see shortly, everything in Angular is evaluated within a scope. We'll learn more
about this in the next steps.
## What are all these files in my working directory?
Most of the files in your working directory come from the {@link
https://github.com/angular/angular-seed angular-seed project} which is typically used to bootstrap
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
scripts and a simple example app, all pre-configured for developing a typical web app.
For the purposes of this tutorial, we modified the angular-seed with the following changes:
* Removed the example app
* Added phone images to `app/img/phones`
* Added phone data files (JSON) to `app/phones`
# Summary
Now let's go to {@link step_01 step 1} and add some content to the web app.
<ul doc:tutorial-nav="0"></ul>
+57
View File
@@ -0,0 +1,57 @@
@ngdoc overview
@name Tutorial: 1 - Static Template
@description
<ul doc:tutorial-nav="1"></ul>
In order to illustrate how angular enhances standard HTML, you will create a purely *static* HTML
page and then examine how we can turn this HTML code into a template that angular will use to
dynamically display the same result with any set of data.
In this step you will add some basic information about two cell phones to an HTML page.
<doc:tutorial-instructions step="1" show="true"></doc:tutorial-instructions>
The page now contains a list with information about two phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-0...step-1 GitHub}:
__`app/index.html`:__
<pre>
...
<ul>
<li>
<span>Nexus S</span>
<p>
Fast just got faster with Nexus S.
</p>
</li>
<li>
<span>Motorola XOOM™ with Wi-Fi</span>
<p>
The Next, Next Generation tablet.
</p>
</li>
</ul>
...
</pre>
# Experiments
* Try adding more static HTML to `index.html`. For example:
<p>Total number of phones: 2</p>
# Summary
This addition to your app uses static HTML to display the list. Now, let's go to {@link step_02
step 2} to learn how to use angular to dynamically generate the same list.
<ul doc:tutorial-nav="1"></ul>
+202
View File
@@ -0,0 +1,202 @@
@ngdoc overview
@name Tutorial: 2 - Angular Templates
@description
<ul doc:tutorial-nav="2"></ul>
Now it's time to make the web page dynamic -- with Angular. We'll also add a test that verifies the
code for the controller we are going to add.
There are many ways to structure the code for an application. For Angular apps, we encourage the
use of {@link http://en.wikipedia.org/wiki/ModelViewController the Model-View-Controller (MVC)
design pattern} to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
The app now contains a list with three phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
## Template for the View
The __view__ component is constructed by Angular from this template:
__`app/index.html`:__
<pre>
...
<body ng:controller="PhoneListCtrl">
<ul>
<li ng:repeat="phone in phones">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
<script src="lib/angular/angular.js" ng:autobind></script>
<script src="js/controllers.js"></script>
</body>
</html>
</pre>
We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat
widget} and two {@link guide/dev_guide.expressions Angular expressions} enclosed in curly braces:
`{{phone.name}}` and `{{phone.snippet}}`:
* The `ng:repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
tag as the template.
<img src="img/tutorial/tutorial_02_final.png">
* The curly braces around `phone.name` and `phone.snippet` are examples of {@link
guide/dev_guide.compiler.markup Angular markup}. The curly markup is shorthand for the Angular
directive {@link api/angular.directive.ng:bind ng:bind}. An `ng:bind` directive indicates a
template binding point to Angular. Binding points are locations in a template where Angular creates
data-binding between the view and the model.
In Angular, the view is a projection of the model through the HTML template. This means that
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
view.
## Model and Controller
The data __model__ (a simple array of phones in object literal notation) is instantiated within
the __controller__ function(`PhoneListCtrl`):
__`app/js/controllers.js`:__
<pre>
function PhoneListCtrl() {
this.phones = [{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S."},
{"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet."},
{"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet."}];
}
</pre>
Although the controller is not yet doing very much controlling, it is playing a crucial role. By
providing context for our data model, the controller allows us to establish data-binding between
the model and the view. We connected the dots between the presentation, data, and logic components
as follows:
* The name of our controller function(in the JavaScript file `controllers.js`) matches the {@link
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
* The data is instantiated within the *scope* of our controller function; our template binding
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
The concept of a scope in Angular is crucial; a scope can be seen as the glue which allows the
template, model and controller to work together. Angular uses scopes, along with the information
contained in the template, data model, and controller, to keep models and views separate, but in
sync. Any changes made to the model are reflected in the view; any changes that occur in the view
are reflected in the model.
To learn more about Angular scopes, see the {@link api/angular.scope angular scope documentation}.
## Tests
The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
unit test for your newly created controller:
__`test/unit/controllersSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function() {
it('should create "phones" model with 3 phones', function() {
var ctrl = new PhoneListCtrl();
expect(ctrl.phones.length).toBe(3);
});
});
});
</pre>
The test verifies that we have three records in the phones array and the example demonstrates how
easy it is to create a unit test for code in Angular. Since testing is such a critical part of
software development, we make it easy to create tests in Angular so that developers are encouraged
to write them.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`./scripts/test-server.sh` to start the test web server.
2. Open a new browser tab or window and navigate to {@link http://localhost:9876}.
3. Choose "Capture this browser in strict mode".
At this point, you can leave this tab open and forget about it. JsTestDriver will use it to
execute the tests and report the results in the terminal.
4. Execute the test by running `./scripts/test.sh`
You should see the following or similar output:
Chrome: Runner reset.
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
Chrome 11.0.696.57 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
Yay! The test passed! Or not...
Note: If you see errors after you run the test, close the browser tab and go back to the terminal
and kill the script, then repeat the procedure above.
# Experiments
* Add another binding to `index.html`. For example:
<p>Total number of phones: {{phones.length}}</p>
* Create a new model property in the controller and bind to it from the template. For example:
this.hello = "Hello, World!"
Refresh your browser to make sure it says, "Hello, World!"
* Create a repeater that constructs a simple table:
<table>
<tr><th>row number</th></tr>
<tr ng:repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>
Now, make the list 1-based by incrementing `i` by one in the binding:
<table>
<tr><th>row number</th></tr>
<tr ng:repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`, and rerun the
`./scripts/test.sh` script.
# Summary
You now have a dynamic app that features separate model, view, and controller components, and you
are testing as you go. Now, let's go to {@link step_03 step 3} to learn how to add full text search
to the app.
<ul doc:tutorial-nav="2"></ul>
+182
View File
@@ -0,0 +1,182 @@
@ngdoc overview
@name Tutorial: 3 - Filtering Repeaters
@description
<ul doc:tutorial-nav="3"></ul>
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end
test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
and quickly detects regressions.
<doc:tutorial-instructions step="3"></doc:tutorial-instructions>
The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3
GitHub}:
## Controller
We made no changes to the controller.
## Template
__`app/index.html`:__
<pre>
...
Fulltext Search: <input ng:model="query"/>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query)">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
We added a standard HTML `<input>` tag and used angular's {@link api/angular.Array.filter $filter}
function to process the input for the `ng:repeater`.
This lets a user enter search criteria and immediately see the effects of their search on the phone
list. This new code demonstrates the following:
* Data-binding. This is one of the core features in Angular. When the page loads, Angular binds the
name of the input box to a variable of the same name in the data model and keeps the two in sync.
In this code, the data that a user types into the input box (named __`query`__) is immediately
available as a filter input in the list repeater (`phone in phones.$filter(`__`query`__`)`). When
changes to the data model cause the repeater's input to change, the repeater efficiently updates
the DOM to reflect the current state of the model.
<img src="img/tutorial/tutorial_03_final.png">
* Use of `$filter`. The {@link api/angular.Array.filter $filter} method uses the `query` value to
create a new array that contains only those records that match the `query`.
`ng:repeat` automatically updates the view in response to the changing number of phones returned
by the `$filter`. The process is completely transparent to the developer.
## Test
In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
controllers and other components of our application written in JavaScript, but they can't easily
test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
better choice.
The search feature was fully implemented via templates and data-binding, so we'll write our first
end-to-end test, to verify that the feature works.
__`test/e2e/scenarios.js`:__
<pre>
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html');
});
it('should filter the phone list as user types into the search box', function() {
expect(repeater('.phones li').count()).toBe(3);
input('query').enter('nexus');
expect(repeater('.phones li').count()).toBe(1);
input('query').enter('motorola');
expect(repeater('.phones li').count()).toBe(2);
});
});
});
</pre>
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
Angular's end-to-end test runner}.
To run the end-to-end test, open one of the following in a new browser tab:
* node.js users: {@link http://localhost:8000/test/e2e/runner.html}
* users with other http servers:
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html}
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
# Experiments
* Display the current value of the `query` model by adding a `{{query}}` binding into the
`index.html` template, and see how it changes when you type in the input box.
* Let's see how we can get the current value of the `query` model to appear in the HTML page title.
You might think you could just add the {{query}} to the title tag element as follows:
<title>Google Phone Gallery: {{query}}</title>
However, when you reload the page, you won't see the expected result. This is because the "query"
model lives in the scope defined by the body element:
<body ng:controller="PhoneListCtrl">
If you want to bind to the query model from the `<title>` element, you must __move__ the
`ng:controller` declaration to the HTML element because it is the common parent of both the body
and title elements:
<html ng:controller="PhoneListCtrl">
Be sure to *remove* the `ng:controller` declaration from the body element.
While using double curlies works fine in within the title element, you might have noticed that
for a split second they are actually displayed to the user while the page is loading. A better
solution would be to use the {@link api/angular.directive.ng:bind ng:bind} or {@link
api/angular.directive.ng:bind-template ng:bind-template} directives, which are invisible to the
user while the page is loading:
<title ng:bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
<pre>
it('should display the current filter value within an element with id "status"',
function() {
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
input('query').enter('nexus');
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
//alternative version of the last assertion that tests just the value of the binding
using('#status').expect(binding('query')).toBe('nexus');
});
</pre>
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
with the `query` binding.
* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this
gives you the opportunity to explore the state of your application while it is displayed in the
browser. The app is live! You can change the search query to prove it. Notice how useful this is
for troubleshooting end-to-end tests.
# Summary
We have now added full text search and included a test to verify that search works! Now let's go on
to {@link step_04 step 4} to learn how to add sorting capability to the phone app.
<ul doc:tutorial-nav="3"></ul>
+198
View File
@@ -0,0 +1,198 @@
@ngdoc overview
@name Tutorial: 4 - Two-way Data Binding
@description
<ul doc:tutorial-nav="4"></ul>
In this step, you will add a feature to let your users control the order of the items in the phone
list. The dynamic ordering is implemented by creating a new model property, wiring it together with
the repeater, and letting the data binding magic do the rest of the work.
<doc:tutorial-instructions step="4"></doc:tutorial-instructions>
You should see that in addition to the search box, the app displays a drop down menu that allows
users to control the order in which the phones are listed.
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 GitHub}:
## Template
__`app/index.html`:__
<pre>
...
<ul class="controls">
<li>
Search: <input type="text" ng:model="query"/>
</li>
<li>
Sort by:
<select ng:model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</li>
</ul>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
We made the following changes to the `index.html` template:
* First, we added a `<select>` html element named `orderProp`, so that our users can pick from the
two provided sorting options.
<img src="img/tutorial/tutorial_04-06_final.png">
* We then chained the `$filter` method with {@link api/angular.Array.orderBy `$orderBy`} method to
further process the input into the repeater. `$orderBy` is a utility method similar to {@link
api/angular.Array.filter `$filter`}, but instead of filtering an array, it reorders it.
Angular creates a two way data-binding between the select element and the `orderProp` model.
`orderProp` is then used as the input for the `$orderBy` method.
As we discussed in the section about data-binding and the repeater in step 3, whenever the model
changes (for example because a user changes the order with the select drop down menu), Angular's
data-binding will cause the view to automatically update. No bloated DOM manipulation code is
necessary!
## Controller
__`app/js/controller.js`:__
<pre>
/* App Controllers */
function PhoneListCtrl() {
this.phones = [{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S.",
"age": 0},
{"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1},
{"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet.",
"age": 2}];
this.orderProp = 'age';
}
</pre>
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
record. This property is used to order phones by age.
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set the default value here, angular would have used the value of the first `<option>` element
(`'name'`) when it initialized the data model.
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
in the controller. So the binding works in the direction from our model to the UI. Now if you
select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
to the model.
## Test
The changes we made should be verified with both a unit test and an end-to-end test. Let's look at
the unit test first.
__`test/unit/controllerSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
ctrl = new PhoneListCtrl();
});
it('should create "phones" model with 3 phones', function() {
expect(ctrl.phones.length).toBe(3);
});
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
});
</pre>
The unit test now verifies that the default ordering property is set.
We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is
shared by all tests in the parent `describe` block.
To run the unit tests, once again execute the `./scripts/test.sh` script and you should see the
following output.
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
Let's turn our attention to the end-to-end test.
__`test/e2e/scenarios.js`:__
<pre>
...
it('should be possible to control phone order via the drop down select box',
function() {
// narrow the dataset to make the test assertions shorter
input('query').enter('tablet');
expect(repeater('.phones li', 'Phone List').column('a')).
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"]);
select('orderProp').option('alphabetical');
expect(repeater('.phones li', 'Phone List').column('a')).
toEqual(["MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"]);
});
...
</pre>
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html
Angular's server}.
# Experiments
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
you'll see that the ordering as well as the current selection in the dropdown menu will default to
"Alphabetical".
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
text.
# Summary
Now that you have added list sorting and tested the app, go to {@link step_05 step 5} to learn
about Angular services and how Angular uses dependency injection.
<ul doc:tutorial-nav="4"></ul>
+216
View File
@@ -0,0 +1,216 @@
@ngdoc overview
@name Tutorial: 5 - XHRs & Dependency Injection
@description
<ul doc:tutorial-nav="5"></ul>
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
from our server using one of angular's built-in {@link api/angular.service services} called {@link
api/angular.service.$xhr $xhr}. We will use angular's {@link guide/dev_guide.di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
<doc:tutorial-instructions step="5"></doc:tutorial-instructions>
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-4...step-5
GitHub}:
## Data
The `app/phones/phone.json` file in your project is a dataset that contains a larger list of phones
stored in the JSON format.
Following is a sample of the file:
<pre>
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
</pre>
## Controller
We'll use angular's {@link api/angular.service.$xhr $xhr} service in our controller to make an HTTP
request to your web server to fetch the data in the `app/phones/phones.json` file. `$xhr` is just
one of several built-in {@link api/angular.service angular services} that handle common operations
in web apps. Angular injects these services for you where you need them.
Services are managed by angular's {@link guide/dev_guide.di DI subsystem}. Dependency injection
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
and control) and loosely coupled (dependencies between components are not resolved by the
components themselves, but by the DI subsystem).
__`app/js/controllers.js:`__
<pre>
function PhoneListCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/phones.json', function(code, response) {
self.phones = response;
});
self.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$xhr'];
</pre>
`$xhr` makes an HTTP GET request to our web server, asking for `phone/phones.json` (the url is
relative to our `index.html` file). The server responds by providing the data in the json file.
(The response might just as well have been dynamically generated by a backend server. To the
browser and our app they both look the same. For the sake of simplicity we used a json file in this
tutorial.)
The `$xhr` service takes a callback as the last argument. This callback is used to process the
response. We assign the response to the scope controlled by the controller, as a model called
`phones`. Notice that angular detected the json response and parsed it for us!
To use a service in angular, you simply declare the names of the services you need as arguments to
the controller's constructor function, as follows:
function PhoneListCtrl($xhr) {...}
Angular's dependency injector provides services to your controller when the controller is being
constructed. The dependency injector also takes care of creating any transitive dependencies the
service may have (services often depend upon other services).
<img src="img/tutorial/xhr_service_final.png">
### '$' Prefix Naming Convention
You can create your own services, and in fact we will do exactly that in step 11. As a naming
convention, angular's built-in services, Scope methods and a few other angular APIs have a '$'
prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
to avoid any possible naming collisions.
### A Note on Minification
Since angular infers the controller's dependencies from the names of arguments to the controller's
constructor function, if you were to {@link http://en.wikipedia.org/wiki/Minification_(programming)
minify} the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
minified as well, and the dependency injector would not being able to identify services correctly.
To overcome issues caused by minification, just assign an array with service identifier strings
into the `$inject` property of the controller function, just like the last line in the snippet
(commented out) suggests:
PhoneListCtrl.$inject = ['$xhr'];
## Test
__`test/unit/controllersSpec.js`:__
Because we started using dependency injection and our controller has dependencies, constructing the
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
constructor with some kind of fake `$xhr` implementation. However, the recommended (and easier) way
is to create a controller in the test environment in the same way that angular does it in the
production code behind the scenes, as follows:
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
$browser.xhr.expectGET('phones/phones.json')
.respond([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
ctrl = scope.$new(PhoneListCtrl);
});
});
</pre>
We created the controller in the test environment, as follows:
* We created a root scope object by calling `angular.scope()`
* We called `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with
the `PhoneListCtrl` controller
Because our code now uses the `$xhr` service to fetch the phone list data in our controller, before
we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an
incoming request from the controller. To do this we:
* Use the {@link api/angular.scope.$service `$service`} method to retrieve the `$browser` service,
a service that angular uses to represent various browser APIs. In tests, angular automatically uses
a mock version of this service that allows you to write tests without having to deal with these
native APIs and the global state associated with them.
* Use the `$browser.xhr.expectGET` method to train the `$browser` object to expect an incoming HTTP
request and tell it what to respond with. Note that the responses are not returned before we call
the `$browser.xhr.flush` method.
Now, we will make assertions to verify that the `phones` model doesn't exist on the scope, before
the response is received:
<pre>
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(ctrl.phones).toBeUndefined();
$browser.xhr.flush();
expect(ctrl.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
</pre>
* We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the callback
we passed into the `$xhr` service to be executed with the trained response.
* We make the assertions, verifying that the phone model now exists on the scope.
Finally, we verify that the default value of `orderProp` is set correctly:
<pre>
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
});
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
# Experiments
* At the bottom of `index.html`, add a `{{phones}}` binding to see the list of phones displayed in
json format.
* In the `PhoneListCtrl` controller, pre-process the xhr response by limiting the number of phones
to the first 5 in the list. Use the following code in the xhr callback:
self.phones = response.splice(0, 5);
# Summary
Now that you have learned how easy it is to use angular services (thanks to angular's
implementation of dependency injection), go to {@link step_06 step 6}, where you will add some
thumbnail images of phones and some links.
<ul doc:tutorial-nav="5"></ul>
+105
View File
@@ -0,0 +1,105 @@
@ngdoc overview
@name Tutorial: 6 - Templating Links & Images
@description
<ul doc:tutorial-nav="6"></ul>
In this step, you will add thumbnail images for the phones in the phone list, and links that, for
now, will go nowhere. In subsequent steps you will use the links to display additional information
about the phones in the catalog.
<doc:tutorial-instructions step="6"></doc:tutorial-instructions>
You should now see links and images of the phones in the list.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-5...step-6
GitHub}:
## Data
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
urls point to the `app/img/phones/` directory.
__`app/phones/phones.json`__ (sample snippet):
<pre>
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]
</pre>
## Template
__`app/index.html`:__
<pre>
...
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar {@link guide/dev_guide.compiler.markup double-curly brace markup} in the `href`
attribute values. In step 2, we added the `{{phone.name}}` binding as the element content. In this
step the `{{phone.id}}` binding is used in the element attribute.
We also added phone images next to each record using an image tag with the {@link
api/angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating
the angular `{{ expression }}` markup literally, which it would have done if we had only specified
an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using
`ng:src` prevents the browser from making an http request to an invalid location.
## Test
__`test/e2e/scenarios.js`__:
<pre>
...
it('should render phone specific links', function() {
input('query').enter('nexus');
element('.phones li a').click();
expect(browser().location().hash()).toBe('/phones/nexus-s');
});
...
</pre>
We added a new end-to-end test to verify that the app is generating correct links to the phone
views that we will implement in the upcoming steps.
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html
angular's server}.
# Experiments
* Replace the `ng:src` directive with a plain old `<src>` attribute. Using tools such as Firebug,
or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or
`/app/index.html/{{phone.imageUrl}}`).
# Summary
Now that you have added phone images and links, go to {@link step_07 step 7} to learn about angular
layout templates and how angular makes it easy to create applications that have multiple views.
<ul doc:tutorial-nav="6"></ul>
+209
View File
@@ -0,0 +1,209 @@
@ngdoc overview
@name Tutorial: 7 - Routing & Multiple Views
@description
<ul doc:tutorial-nav="7"></ul>
In this step, you will learn how to create a layout template and how to build an app that has
multiple views by adding routing.
<doc:tutorial-instructions step="7"></doc:tutorial-instructions>
Note that you are redirected to `app/index.html#/phones` and the same phone list appears in the
browser. When you click on a phone link the stub of a phone detail page is displayed.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-6...step-7
GitHub}:
## Multiple Views, Routing and Layout Template
Our app is slowly growing and becoming more complex. Before step 7, the app provided our users with
a single view (the list of all phones), and all of the template code was located in the
`index.html` file. The next step in building the app is to add a view that will show detailed
information about each of the devices in our list.
To add the detailed view, we could expand the `index.html` file to contain template code for both
views, but that would get messy very quickly. Instead, we are going to turn the `index.html`
template into what we call a "layout template". This is a template that is common for all views in
our application. Other "partial templates" are then included into this layout template depending on
the current "route" — the view that is currently displayed to the user.
Application routes in angular are declared via the {@link api/angular.service.$route $route}
service. This service makes it easy to wire together controllers, view templates, and the current
URL location in the browser. Using this feature we can implement {@link
http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's
history (back and forward navigation) and bookmarks.
## Controllers
__`app/js/controller.js`:__
<pre>
function PhoneCatCtrl($route) {
var self = this;
$route.when('/phones',
{template: 'partials/phone-list.html', controller: PhoneListCtrl});
$route.when('/phones/:phoneId',
{template: 'partials/phone-detail.html', controller: PhoneDetailCtrl});
$route.otherwise({redirectTo: '/phones'});
$route.onChange(function() {
self.params = $route.current.params;
});
$route.parent(this);
}
//PhoneCatCtrl.$inject = ['$route'];
...
</pre>
We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route`
service and used this service to declare that our application consists of two different views:
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, angular will use the
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
The statement `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when
the browser address doesn't match either of our routes.
Thanks to the `$route.parent(this);` statement and `ng:controller="PhoneCatCtrl"` declaration in
the `index.html` template, the `PhoneCatCtrl` controller has a special role in our app. It is the
"root" controller and the parent controller for the other two sub-controllers (`PhoneListCtrl` and
`PhoneDetailCtrl`). The sub-controllers inherit the model properties and behavior from the root
controller.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
URL. All variables defined with the `:` notation are extracted into the `$route.current.params` map.
The `params` alias created in the {@link api/angular.service.$route `$route.onChange`} callback
allows us to use the `phoneId` property of this map in the `phone-details.html` template.
## Template
The `$route` service is usually used in conjunction with the {@link api/angular.widget.ng:view
ng:view} widget. The role of the `ng:view` widget is to include the view template for the current
route into the layout template, which makes it a perfect fit for our `index.html` template.
__`app/index.html`:__
<pre>
...
<body ng:controller="PhoneCatCtrl">
<ng:view></ng:view>
<script src="lib/angular/angular.js" ng:autobind></script>
<script src="js/controllers.js"></script>
</body>
</html>
</pre>
Note that we removed most of the code in the `index.html` template and replaced it with a single
line containing the `ng:view` tag. The code that we removed was placed into the `phone-list.html`
template:
__`app/partials/phone-list.html`:__
<pre>
<ul class="predicates">
<li>
Search: <input type="text" ng:model="query"/>
</li>
<li>
Sort by:
<select ng:model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</li>
</ul>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
<p>{{phone.snippet}}</p>
</li>
</ul>
</pre>
<img src="img/tutorial/tutorial_07_final.png">
We also added a placeholder template for the phone details view:
__`app/partials/phone-detail.html`:__
<pre>
TBD: detail view for {{params.phoneId}}
</pre>
Note how we are using `params` model defined in the `PhoneCatCtrl` controller.
## Test
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
to various URLs and verify that the correct view was rendered.
<pre>
...
it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('../../app/index.html');
expect(browser().location().hash()).toBe('/phones');
});
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display placeholder page with phoneId', function() {
expect(binding('params.phoneId')).toBe('nexus-s');
});
});
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html
angular's server}.
# Experiments
* Try to add an `{{orderProp}}` binding to `index.html`, and you'll see that nothing happens even
when you are in the phone list view. This is because the `orderProp` model is visible only in the
scope managed by `PhoneListCtrl`, which is associated with the `<ng:view>` element. If you add the
same binding into the `phone-list.html` template, the binding will work as expected.
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
inheritance and model property shadowing do some wonders.
# Summary
With the routing set up and the phone list view implemented, we're ready to go to {@link step_08
step 8} to implement the phone details view.
<ul doc:tutorial-nav="7"></ul>
+188
View File
@@ -0,0 +1,188 @@
@ngdoc overview
@name Tutorial: 8 - More Templating
@description
<ul doc:tutorial-nav="8"></ul>
In this step, you will implement the phone details view, which is displayed when a user clicks on a
phone in the phone list.
<doc:tutorial-instructions step="8"></doc:tutorial-instructions>
Now when you click on a phone on the list, the phone details page with phone-specific information
is displayed.
To implement the phone details view we will use {@link api/angular.service.$xhr $xhr} to fetch our
data, and we'll flesh out the `phone-details.html` view template.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-7...step-8
GitHub}:
## Data
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
phone:
__`app/phones/nexus-s.json`:__ (sample snippet)
<pre>
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}
</pre>
Each of these files describes various properties of the phone using the same data structure. We'll
show this data in the phone detail view.
## Controller
We'll expand the `PhoneDetailCtrl` by using the `$xhr` service to fetch the json files. This works
the same way as the phone list controller.
__`app/js/controller.js`:__
<pre>
function PhoneDetailCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
self.phone = response;
});
}
//PhoneDetailCtrl.$inject = ['$xhr'];
</pre>
To construct the URL for the HTTP request, we use `params.phoneId` extracted from the current route
in the `PhoneCatCtrl` controller.
## Template
The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
Note where we use the angular `{{expression}}` markup and `ng:repeater`s to project phone data from
our model into the view.
__`app/partials/phone-details.html`:__
<pre>
<img ng:src="{{phone.images[0]}}" class="phone"/>
<h1>{{phone.name}}</h1>
<p>{{phone.description}}</p>
<ul class="phone-thumbs">
<li ng:repeat="img in phone.images">
<img ng:src="{{img}}"/>
</li>
</ul>
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng:repeat="availability in phone.availability">{{availability}}</dd>
</dl>
</li>
...
</li>
<span>Additional Features</span>
<dd>{{phone.additionalFeatures}}</dd>
</li>
</ul>
</pre>
<img src="img/tutorial/tutorial_08-09_final.png">
## Test
We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in
step 5.
__`test/unit/controllerSpec.js`:__
<pre>
...
it('should fetch phone detail', function() {
scope.params = {phoneId:'xyz'};
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
ctrl = scope.$new(PhoneDetailCtrl);
expect(ctrl.phone).toBeUndefined();
$browser.xhr.flush();
expect(ctrl.phone).toEqual({name:'phone xyz'});
});
...
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
...
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms)
Chrome 11.0.696.57 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
heading on the page is "Nexus S".
__`test/e2e/scenarios.js`:__
<pre>
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display nexus-s page', function() {
expect(binding('phone.name')).toBe('Nexus S');
});
});
...
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
angular's server}.
# Experiments
* Using the {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
end-to-end test runner API}, write a test that verifies that we display 4 thumbnail images on the
Nexus S details page.
# Summary
Now that the phone details view is in place, proceed to {@link step_09 step 9} to learn how to
write your own custom display filter.
<ul doc:tutorial-nav="8"></ul>
+121
View File
@@ -0,0 +1,121 @@
@ngdoc overview
@name Tutorial: 9 - Filters
@description
<ul doc:tutorial-nav="9"></ul>
In this step you will learn how to create your own custom display filter.
<doc:tutorial-instructions step="9"></doc:tutorial-instructions>
Navigate to one of the detail pages.
In the previous step, the details page displayed either "true" or "false" to indicate whether
certain phone features were present or not. We have used a custom filter to convert those text
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see, what the filter code looks like.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-8...step-9
GitHub}:
## Custom Filter
In order to create a new filter, simply register your custom filter function with the {@link
api/angular.filter `angular.filter`} API.
__`app/js/filters.js`:__
<pre>
angular.filter('checkmark', function(input) {
return input ? '\u2713' : '\u2718';
});
</pre>
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
return one of two unicode characters we have chosen to represent true or false (`\u2713` and
`\u2718`).
## Template
Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our
layout template.
__`app/index.html`:__
<pre>
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...
</pre>
The syntax for using filters in angular templates is as follows:
{{ expression | filter }}
Let's employ the filter in the phone details template:
__`app/partials/phone-detail.html`:__
<pre>
...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...
</pre>
## Test
Filters, like any other component, should be tested and these tests are very easy to write.
__`test/unit/filtersSpec.js`:__
<pre>
describe('checkmark filter', function() {
it('should convert boolean values to unicode checkmark or cross', function() {
expect(angular.filter.checkmark(true)).toBe('\u2713');
expect(angular.filter.checkmark(false)).toBe('\u2718');
});
})
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
# Experiments
* Let's experiment with some of the {@link api/angular.filter built-in angular filters} and add the
following bindings to `index.html`:
* `{{ "lower cap string" | uppercase }}`
* `{{ {foo: "bar", baz: 23} | json }}`
* `{{ 1304375948024 | date }}`
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
* We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:
<input ng:model="userInput"> Uppercased: {{ userInput | uppercase }}
# Summary
Now that you have learned how to write and test a custom filter, go to {@link step_10 step 10} to
learn how we can use angular to enhance the phone details page further.
<ul doc:tutorial-nav="9"></ul>
+140
View File
@@ -0,0 +1,140 @@
@ngdoc overview
@name Tutorial: 10 - Event Handlers
@description
<ul doc:tutorial-nav="10"></ul>
In this step, you will add a clickable phone image swapper to the phone details page.
<doc:tutorial-instructions step="10"></doc:tutorial-instructions>
The phone details view displays one large image of the current phone and several smaller thumbnail
images. It would be great if we could replace the large image with any of the thumbnails just by
clicking on the desired thumbnail image. Let's have a look at how we can do this with angular.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-9...step-10
GitHub}:
## Controller
__`app/js/controllers.js`:__
<pre>
...
function PhoneDetailCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
self.phone = response;
self.mainImageUrl = response.images[0];
});
self.setImage = function(imageUrl) {
self.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.$inject = ['$xhr'];
</pre>
In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its
default value to the first phone image url.
We also created a `setImage` controller method to change the value of `mainImageUrl`.
## Template
__`app/partials/phone-detail.html`:__
<pre>
<img ng:src="{{mainImageUrl}}" class="phone"/>
...
<ul class="phone-thumbs">
<li ng:repeat="img in phone.images">
<img ng:src="{{img}}" ng:click="setImage(img)">
</li>
</ul>
...
</pre>
We bound the `ng:src` attribute of the large image to the `mainImageUrl` property.
We also registered an {@link api/angular.directive.ng:click `ng:click`} handler with thumbnail
images. When a user clicks on one of the thumbnail images, the handler will use the `setImage`
controller method to change the value of the `mainImageUrl` property to the url of the thumbnail
image.
<img src="img/tutorial/tutorial_10-11_final.png">
## Test
To verify this new feature, we added two end-to-end tests. One verifies that the main image is set
to the first phone image by default. The second test clicks on several thumbnail images and
verifies that the main image changed appropriately.
__`test/e2e/scenarios.js`:__
<pre>
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display the first phone image as the main phone image', function() {
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
it('should swap main image if a thumbnail image is clicked on', function() {
element('.phone-thumbs li:nth-child(3) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
element('.phone-thumbs li:nth-child(1) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
});
});
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
angular's server}.
# Experiments
* Let's add a new controller method to `PhoneCatCtrl`:
this.hello = function(name) {
alert('Hello ' + (name || 'world') + '!');
}
and add:
<button ng:click="hello('Elmo')">Hello</button>
to the `index.html` template.
The controller methods are inherited between controllers/scopes, so you can use the same snippet
in the `phone-list.html` template as well.
* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
declared in `index.html` will stop working, while the one declared in the `phone-list.html`
template remains operational.
# Summary
With the phone image swapper in place, we're ready for {@link step_11 step 11} (the last step!) to
learn an even better way to fetch data.
<ul doc:tutorial-nav="10"></ul>
+208
View File
@@ -0,0 +1,208 @@
@ngdoc overview
@name Tutorial: 11 - REST and Custom Services
@description
<ul doc:tutorial-nav="11"></ul>
In this step, you will improve the way our app fetches data.
<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
The last improvement we will make to our app is to define a custom service that represents a {@link
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we
can make xhr requests for data in an easier way, without having to deal with the lower-level {@link
api/angular.service.$xhr $xhr} API, HTTP methods and URLs.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-10...step-11
GitHub}:
## Template
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
template:
__`app/index.html`.__
<pre>
...
<script src="js/services.js"></script>
...
</pre>
## Service
__`app/js/services.js`.__
<pre>
angular.service('Phone', function($resource) {
return $resource('phones/:phoneId.json', {}, {
query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true}
});
});
</pre>
We used the {@link api/angular.service} API to register a custom service. We passed in the name of
the service - 'Phone' - and a factory function. The factory function is similar to a controller's
constructor in that both can declare dependencies via function arguments. The Phone service
declared a dependency on the `$resource` service.
The {@link api/angular.service.$resource `$resource`} service makes it easy to create a {@link
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines
of code. This client can then be used in our application, instead of the lower-level {@link
api/angular.service.$xhr $xhr} service.
## Controller
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
lower-level {@link api/angular.service.$xhr $xhr} service, replacing it with a new service called
`Phone`. Angular's {@link api/angular.service.$resource `$resource`} service is easier to use than
{@link api/angular.service.$xhr $xhr} for interacting with data sources exposed as RESTful
resources. It is also easier now to understand what the code in our controllers is doing.
__`app/js/controllers.js`.__
<pre>
...
function PhoneListCtrl(Phone) {
this.orderProp = 'age';
this.phones = Phone.query();
}
//PhoneListCtrl.$inject = ['Phone'];
function PhoneDetailCtrl(Phone) {
var self = this;
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
self.mainImageUrl = phone.images[0];
});
...
}
//PhoneDetailCtrl.$inject = ['Phone'];
</pre>
Notice how in `PhoneListCtrl` we replaced:
$xhr('GET', 'phones/phones.json', function(code, response) {
self.phones = response;
});
with:
this.phones = Phone.query();
This is a simple statement that we want to query for all phones.
An important thing to notice in the code above is that we don't pass any callback functions when
invoking methods of our Phone service. Although it looks as if the result were returned
synchronously, that is not the case at all. What is returned synchronously is a "future" — an
object, which will be filled with data when the xhr response returns. Because of the data-binding
in angular, we can use this future and bind it to our template. Then, when the data arrives, the
view will automatically update.
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
we require, so in these cases, we can add a callback to process the server response. The
`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback.
## Test
We have modified our unit tests to verify that our new service is issuing HTTP requests and
processing them as expected. The tests also check that our controllers are interacting with the
service correctly.
The {@link api/angular.service.$resource $resource} service augments the response object with
methods for updating and deleting the resource. If we were to use the standard `toEqual` matcher,
our tests would fail because the test values would not match the responses exactly. To solve the
problem, we use a newly-defined `toEqualData` {@link
http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}. When the
`toEqualData` matcher compares two objects, it takes only object properties into account and
ignores methods.
__`test/unit/controllersSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
beforeEach(function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
describe('PhoneListCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
$browser.xhr.expectGET('phones/phones.json')
.respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
ctrl = scope.$new(PhoneListCtrl);
});
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(ctrl.phones).toEqual([]);
$browser.xhr.flush();
expect(ctrl.phones).toEqualData([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
describe('PhoneDetailCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
});
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
});
it('should fetch phone detail', function() {
scope.params = {phoneId:'xyz'};
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
ctrl = scope.$new(PhoneDetailCtrl);
expect(ctrl.phone).toEqualData({});
$browser.xhr.flush();
expect(ctrl.phone).toEqualData({name:'phone xyz'});
});
});
});
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
# Summary
There you have it! We have created a web app in a relatively short amount of time. In the {@link
the_end closing notes} we'll cover were to go from here.
<ul doc:tutorial-nav="11"></ul>
+21
View File
@@ -0,0 +1,21 @@
@ngdoc overview
@name Tutorial: The End
@description
Our application is now complete. Feel free to experiment with the code further, and jump back to
previous steps using the `git checkout` or `goto_step.sh` commands.
For more details and examples of the angular concepts we touched on in this tutorial, see the
{@link guide/ Developer Guide}.
For several more examples of code, see the {@link cookbook/ Cookbook}.
When you are ready to start developing a project using angular, we recommend that you bootstrap
your development with the {@link https://github.com/angular/angular-seed angular seed} project.
We hope this tutorial was useful to you and that you learned enough about angular to make you want
to learn more. We especially hope you are inspired to go out and develop angular web apps of your
own, and that you might be interested in {@link misc/contribute contributing} to angular.
If you have questions or feedback or just want to say "hi", please post a message at {@link
https://groups.google.com/forum/#!forum/angular}.
-101
View File
@@ -1,101 +0,0 @@
@workInProgress
@ngdoc overview
@name Cookbook: Advanced Form
@description
Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
function UserForm(){
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
this.cancel();
}
UserForm.prototype = {
cancel: function(){
this.form = angular.copy(this.master);
},
save: function(){
this.master = this.form;
this.cancel();
}
};
</script>
<div ng:controller="UserForm">
<label>Name:</label><br/>
<input type="text" name="form.name" ng:required/> <br/><br/>
<label>Address:</label><br/>
<input type="text" name="form.address.line1" size="33" ng:required/> <br/>
<input type="text" name="form.address.city" size="12" ng:required/>,
<input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="form.address.zip" size="5" ng:required ng:validate="regexp:zip"/><br/><br/>
<label>Phone:</label>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
<div ng:repeat="contact in form.contacts">
<select name="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<button ng:click="cancel()" disabled="{{master.$equals(form)}}">Cancel</button>
<button ng:click="save()" disabled="{{$invalidWidgets.visible() || master.$equals(form)}}">Save</button>
<hr/>
Debug View:
<pre>form={{form}}
master={{master}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function(){
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function(){
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
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');
});
</doc:scenario>
</doc:example>
#Things to notice
* Cancel & save buttons are only enabled if the form is dirty -- there is something to cancel or
save.
* Save button is only enabled if there are no validation errors on the form.
* Cancel reverts the form changes back to original state.
* Save updates the internal model of the form.
* Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.
+2
View File
@@ -0,0 +1,2 @@
controller: {{name}}<br />
Book Id: {{params.bookId}}<br />

Some files were not shown because too many files have changed in this diff Show More