Compare commits

...

78 Commits

Author SHA1 Message Date
Igor Minar 20687aa5f6 chore(release): cutting 1.0.0rc3 barefoot-telepathy 2012-03-29 16:10:40 -07:00
Igor Minar fc52b81d52 fix(docs): update the example widget regexp for detecting angular.js url
so that we don't show angular-cookies instead of angular.js
2012-03-29 16:10:40 -07:00
Igor Minar ae1aee2b6c fix(FormController): ask for dependency to fool the BC module 2012-03-29 16:10:40 -07:00
Igor Minar 423242017e fix(docs): properly rewrite urls in doc examples at docs-next 2012-03-29 16:10:40 -07:00
Vojta Jina 95c5df5958 fix(ngValue): bind properly inside ng-repeat 2012-03-29 14:05:19 -07:00
Igor Minar 2cb907a836 fix($injector): properly infer dependencies from fn with no args
Previously if there was a white-space in fn: fn( ) {} we failed to infer no args.

This was originally reported by recht, but I decided to use a different fix.

Closes #829
2012-03-29 11:21:04 -07:00
Igor Minar 2f2fd465a4 docs(changelog): release notes for 1.0.0rc3 barefoot-telepathy 2012-03-29 08:10:28 -07:00
Vojta Jina 6da355c3e1 refactor($compile): move methods of attr object into prototype
We have many instances of this object and we clone them as well (e.g. ng-repeat).
This should save some memory and performance as well.

Double prefixed private properties of attr object:
attr.$element -> attr.$$element
attr.$observers -> attr.$$observers

Update shallowCopy to not copy $$ properties and allow passing optional destination object.
2012-03-29 07:30:32 -07:00
Vojta Jina f2106692b1 fix($compile): properly clone attr.$observers in ng-repeat
The `attr` object was only shallow copied which caused all observers to be shared.
Fixing similar issue in ng-* boolean attributes as well as ng-src and ng-href.
2012-03-29 07:30:32 -07:00
Vojta Jina 4557881cf8 chore(release scripts): auto release scripts 2012-03-29 07:22:13 -07:00
Igor Minar af0ad6561c refactor(fromJson/toJson): move the contents of these files into Angular.js
these files are now mostly empty so it doesn't make sense to keep them
separated from other helper functions
2012-03-28 16:57:34 -07:00
Igor Minar 35125d2513 refactor(toJson): use native JSON.stringify
Instead of using our custom serializer we now use the native one and
use the replacer function to customize the serialization to preserve
most of the previous behavior (ignore $ and $$ properties as well
as window, document and scope instances).
2012-03-28 16:57:22 -07:00
Igor Minar 87f5c6e5b7 refactor(fromJson): always use native JSON.parse
This breaks IE7 for which you can use polyfill:

https://github.com/douglascrockford/JSON-js

<!--[if lt IE 8]>
<script src="json2.min.js"></script>
<![endif]-->

or

http://bestiejs.github.com/json3/

<!--[if lt IE 8]>
<script src="json3.min.js"></script>
<![endif]-->
2012-03-28 16:30:38 -07:00
Igor Minar a8a750ab05 feat($http): make the transform defaults to an array
$httpProvider.defaults.transformRequest and $httpProvider.defaults.transformResponse
are now arrays containing single function. This makes it easy to add an
extra transform fn.

adding an extra fn before had to be done in this cluncky way:

$httpProvider.defaults.transformResponse =
[$httpProvider.defaults.transformResponse, myTransformFn];

after this change, it's simply:

$httpProvider.defaults.transformResponse.push(myTransformFn);
2012-03-28 16:30:38 -07:00
Igor Minar 13a95ae499 style($http): remove redundant 'use strict' header 2012-03-28 16:30:31 -07:00
Igor Minar da9f4dfcf4 feat(TzDate): add support for toISOString method 2012-03-28 16:30:31 -07:00
Igor Minar ac4318a2fa refactor(fromJson/date filter): move date string logic to date filter
Breaks angular.fromJson which doesn't deserialize date strings into date objects.

This was done to make fromJson compatible with JSON.parse.

If you do require the old behavior - if at all neeeded then because of
json deserialization of XHR responses - then please create a custom
$http transform:

$httpProvider.defaults.transformResponse.push(function(data) {
  // recursively parse dates from data object here
  // see code removed in this diff for hints
});

Closes #202
2012-03-28 16:30:30 -07:00
Misko Hevery bb2fa6f63f fix(i18n e2e tests): 2012-03-28 11:24:47 -07:00
Igor Minar ba59ef4950 docs(examples): update example apps 2012-03-28 11:24:47 -07:00
Igor Minar 8b93541522 style(Rakefile): use snake_case in ruby code 2012-03-28 11:16:36 -07:00
Misko Hevery 7b22d59b4a chore(ngCookies): moved to module 2012-03-28 11:16:36 -07:00
Misko Hevery 798bca62c6 chore(resource): moved to module 2012-03-28 11:16:36 -07:00
Misko Hevery 8218c4b60b chore(Rakefile): get ready for modules 2012-03-28 11:16:36 -07:00
Misko Hevery 2430f52bb9 chore(module): move files around in preparation for more modules 2012-03-28 11:16:35 -07:00
Brad Green 944098a4e0 Updated manual bootstrap document
Explained why you'd want to manually bootstrap, added contrasting
example for automatic vs manual methods.
2012-03-27 18:06:00 -07:00
Brad Green 2ce0485e6f Rewrite of Automatic Initialization doc
Added examples, explained the reasons why you initialize the whole app
or parts of the page.
2012-03-27 08:28:34 -07:00
Vojta Jina a08cbc02e7 feat($compile): do not interpolate boolean attributes, rather evaluate them
So that we can have non string values, e.g. ng-value="true" for radio inputs

Breaks boolean attrs are evaluated rather than interpolated

To migrate your code, change: <input ng-disabled="{{someBooleanVariable}}">
to: <input ng-disabled="someBooleanVariabla">


Affected directives:

* ng-multiple
* ng-selected
* ng-checked
* ng-disabled
* ng-readonly
* ng-required
2012-03-26 21:14:09 -07:00
Vojta Jina 55027132f3 refactor(ngBindAttr): remove
Breaks ng-bind-attr directive removed
2012-03-26 21:14:09 -07:00
Vojta Jina 09e175f02c feat(ngValue): allow radio inputs to have non string values
Closes #816
2012-03-26 21:14:09 -07:00
Mykhailo Kotsur 5c5b1183c8 docs(guide/module): fix syntax error and expectation in test example 2012-03-26 16:06:46 -07:00
Mykhailo Kotsur f04142ea28 docs(guide/unit-testing): fixed typo in code example 2012-03-26 16:06:16 -07:00
Igor Minar aaedefb92e refactor($sniffer): make $sniffer service private
This service has been accidentaly documented in the past, it should not be considered
to be public api.

I'm also removing fallback to Modernizr since we don't need it.

Breaks any app that depends on this service and its fallback to Modernizr, please
migrate to custom "Modernizr" service:

    module.value('Modernizr', function() { return Modernizr; });
2012-03-26 15:43:59 -07:00
Igor Minar d54dfecb00 feat($controller): support controller registration via $controllerProvider
It's now possible to register controllers as:

.register('MyCtrl', function($scope) { ... });
// or
.register('MyCtrl', ['$scope', function($scope) { ... });

Additionally a module loader shortcut api was added as well:

myModule.controller('MyCtr', function($scope) { ... });
2012-03-26 15:23:29 -07:00
Igor Minar 4b8d926062 feat(assertArgFn): should support array annotated fns 2012-03-26 12:21:42 -07:00
Igor Minar 74c84501ed doc(guide/module): fix typo 2012-03-23 16:57:24 -07:00
Igor Minar 4581b79bbd doc(guide/controller): fix examples 2012-03-23 16:54:48 -07:00
Manuel Woelker 2be8847ef6 doc(guide): order topic list in guide sidebar in accordance with overview
Closes #405
2012-03-23 16:31:33 -07:00
Igor Minar cb2ad9abf2 fix(init): use jQuery#ready for init if available
Closes #818
2012-03-23 15:41:37 -07:00
Misko Hevery 73c8593077 feat(http): added params parameter
The params parameter can now be used to serialize parameters in the URLs. The serialization does proper escaping and JSON encoding if it is an object.
2012-03-23 14:21:43 -07:00
Misko Hevery ac75079e21 fix(q): resolve all of nothing to nothing
$q.all([]) no longer throws exception and resolves to empty array []
2012-03-23 14:21:43 -07:00
Igor Minar 5390fb37d2 fix($compile): create new (isolate) scopes for directives on root elements
previously we would not create them and it's causing all kinds of issues and accidental leaks

Closes #817
2012-03-23 11:46:54 -07:00
Igor Minar 8d7e694849 fix(forEach): should ignore prototypically inherited properties
Closes #813
2012-03-22 16:39:36 -07:00
Igor Minar 5fdab52dd7 feat(jqLite): make injector() and scope() work with the document object
For typical app that has ng-app directive on the html element, we now can do:

angular.element(document).injector() or .injector()
angular.element(document).scope() or .scope()

instead of:

angular.element(document.getElementsByTagName('html')[0]).injector()
...
2012-03-22 16:39:36 -07:00
Vojta Jina 541bedd1a9 refactor(ngController): remove unused deps 2012-03-22 16:29:31 -07:00
Igor Minar 98e18a64aa docs(cookbook/form): fix the example
Closes #712
2012-03-21 13:52:11 -07:00
Igor Minar 0a45bff472 chore(docs): switch disqus id from angularjs to angularjs-next 2012-03-21 13:46:35 -07:00
Igor Minar 263524d381 docs(changelog): fix rc2 release date 2012-03-20 17:21:41 -07:00
Igor Minar 52c59cf0ce chore(release): start 1.0.0rc barefoot-telepathy iteration 2012-03-20 16:02:49 -07:00
Igor Minar c5f8edfe03 chore(release): cutting the 1.0.0rc2 silence-absorption release 2012-03-20 15:38:57 -07:00
Igor Minar 69f0aa899d docs(changelog): release notes for 1.0.0rc2 silence-absorption 2012-03-20 15:25:31 -07:00
Daniel Zen e7cd0bcc5a docs(guide/controllers): add a section on testing controllers 2012-03-20 15:23:58 -07:00
Vojta Jina ade6c45275 feat(input.radio): Allow value attribute to be interpolated 2012-03-20 14:39:23 -07:00
Igor Minar 9eafd10fcd docs(guide/location): fix example 2012-03-20 12:05:57 -07:00
Igor Minar 3436c027f2 docs(guide/started): fix examples 2012-03-20 11:30:21 -07:00
Igor Minar 6a8749e65a refactor($resource): unify and simplify the code 2012-03-20 11:07:38 -07:00
Igor Minar 1a5bebd927 fix($http): don't send Content-Type header when no data
When a http request has no data (body), we should not send the
Content-Type header as it causes problems for some server-side
frameworks.

Closes #749
2012-03-20 11:07:38 -07:00
Igor Minar 83155e8fbe style(ResourceSpec): style clean up 2012-03-20 11:07:37 -07:00
Igor Minar 6d6f875345 fix($resource): support escaping of ':' in resource url
So one can how define cors/jsonp resources with port number as:

resource.route('http://localhost\\:8080/Path')
2012-03-20 11:07:37 -07:00
Igor Minar a4fe51da3b feat($route): when matching consider trailing slash as optional
This makes for a much more flexible route matching:

- route /foo matches /foo and redirects /foo/ to /foo
- route /bar/ matches /bar/ and redirects /bar to /bar/

Closes #784
2012-03-20 11:07:37 -07:00
Igor Minar ee5a5352fd fix(e2e runner): fix typo that caused errors on IE8
Closes #806
2012-03-20 11:07:37 -07:00
Igor Minar 9cb2195e61 fix($compile): don't touch static element attributes
Compiler should not reassign values to element attributes if its not neccessary due
to interpolation or special attribute magic (ng-src -> src)

This resolves several issues on IE caused by reassigning script.src attribute which
caused all of the scripts to be reloaded.
2012-03-20 11:07:36 -07:00
Igor Minar 15213ec212 fix($log): avoid console.log.apply calls in IE
In IE window.console.log and friends are functions that don't have apply or call fns.

For this reason we have to treat them specially and do our best to log at least
something when running in this browser.

Closes #805
2012-03-20 11:07:36 -07:00
Igor Minar 9171c76bb4 style($log): reformat code for readability 2012-03-20 11:07:35 -07:00
Igor Minar 64fb1f2620 docs(filters): use ng-model-instant in live examples
Closes #807
2012-03-20 11:07:35 -07:00
Vojta Jina f49eaf8bf2 fix($compile): Merge interpolated css class when replacing an element 2012-03-20 10:39:43 -07:00
Vojta Jina f701ce08f9 fix(matchers.toHaveClass): Correct reference to angular.mock.dump 2012-03-19 17:26:29 -07:00
Misko Hevery 1cc0e4173d bug(ie7): incorrectly set all inputs to disabled
In ie7 all of the input fields are set to readonly and disabled, because ie7 enumerates over all attributes even if the are not declared on the element.
2012-03-19 15:49:42 -07:00
Misko Hevery d4ae7988da chore(parseInt): cleanup parseInt() for our int() 2012-03-19 11:41:23 -07:00
Misko Hevery 5ac14f633a fix(json): added support for iso8061 timezone
Added support of timezone in dates not just zulu timezone.

This fixes issues for date filter which uses json deserialization under the hood. (for now)

Closes #/800
2012-03-19 11:41:10 -07:00
Misko Hevery 9918b748be fix(compiler): allow transclusion of root elements
Fixed an issue where a directive that uses transclusion (such as ngRepeat) failed to link if it was declared on the root element of the compilation tree. (For example ngView or ngInclude including template where ngRepeat was the top most element).
2012-03-19 11:35:10 -07:00
Misko Hevery 6ecac8e71a fix(select): multiselect failes to update view on selection insert
In multiselect when the underlying selection array push/pops an element the view did not re-render since the array reference stayed the same.
2012-03-19 11:35:10 -07:00
Misko Hevery 823adb2319 fix(ngForm): alias name||ngForm
form directive was requiring name attribute even when invoked as attribute, resulting in unnecessary duplication
2012-03-19 11:35:09 -07:00
Misko Hevery 21e74c2d2e fix(ngView): controller not published
corrected omitted assignment of controller to the element data object. Without this fix the controller created by ngView is not accessible from the browser debugger.
2012-03-19 11:35:09 -07:00
Misko Hevery 6c5a05ad49 feat(jqLite): add .controller() method
extend JQuery with .controller() method which retrieves the closest controller for a given element
2012-03-19 11:35:09 -07:00
Vojta Jina 192ff61f5d feat(scope.$eval): Allow passing locals to the expression 2012-03-18 23:46:30 -07:00
Igor Minar 935c1018da fix(ngRepeat): correct variable reference in error message
Closese #803
2012-03-17 15:57:55 -07:00
Igor Minar 78a6291666 docs(scope): add $destroy event docs 2012-03-16 15:32:14 -07:00
Igor Minar 53b6f522a5 fix(ngDocSpec): fix broken tests 2012-03-16 15:32:14 -07:00
362 changed files with 3104 additions and 2123 deletions
+114
View File
@@ -1,3 +1,117 @@
<a name="v1.0.0rc3"></a>
# v1.0.0rc3 barefoot-telepathy (2012-03-29)
## Bug Fixes
- **$compile:**
- properly clone attr.$observers in ng-repeat
([f2106692](https://github.com/angular/angular.js/commit/f2106692b1ebf00aa5f8b2accd75f014b6cd4faa))
- create new (isolate) scopes for directives on root elements
([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787),
closes [#817](https://github.com/angular/angular.js/issues/817))
- **angular.forEach:** should ignore prototypically inherited properties
([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61),
closes [#813](https://github.com/angular/angular.js/issues/813))
- **initialization:** use jQuery#ready for initialization if available
([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d),
closes [#818](https://github.com/angular/angular.js/issues/818))
- **$q:** resolve all of nothing to nothing
([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
## Features
- **$compile:** do not interpolate boolean attribute directives, rather evaluate them
([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- **$controller:** support controller registration via $controllerProvider
([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
- **$http:**
- make the `transformRequest` and `transformResponse` default to an array
([a8a750ab](https://github.com/angular/angular.js/commit/a8a750ab05bdff73ba3af0b98f3f284ff8d1e743))
- added `params` parameter
([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
- **TzDate:** add support for toISOString method
([da9f4dfc](https://github.com/angular/angular.js/commit/da9f4dfcf4f3d0c21821d8474ac0bb19a3c51415))
- **jqLite:** make injector() and scope() work with the document object
([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
- **ngValue:** directive that allows radio inputs to have non string values
([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b),
closes [#816](https://github.com/angular/angular.js/issues/816))
## Breaking Changes
- `$resource`, `$cookies` and `$cookieStore` services are now distributed as separate modules, see
`angular-resource.js` and `angular-cookies.js`.
([798bca62](https://github.com/angular/angular.js/commit/798bca62c6f64775b85deda3713e7b6bcc7a4b4d),
[7b22d59b](https://github.com/angular/angular.js/commit/7b22d59b4a16d5c50c2eee054178ba17f8038880))
- angular.fromJson doesn't deserialize date strings into date objects.
([ac4318a2](https://github.com/angular/angular.js/commit/ac4318a2fa5c6d306dbc19466246292a81767fca))
- angular.toJson always use native JSON.parse and JSON.stringify - this might break code that
consumes the output in whitespace-sensitive way
([35125d25](https://github.com/angular/angular.js/commit/35125d25137ac2da13ed1ca3e652ec8f2c945053))
- IE7 and older have are now required to polyfill the JSON global object
([87f5c6e5](https://github.com/angular/angular.js/commit/87f5c6e5b716100e203ec59c5874c3e927f83fa0))
- boolean attr directives (ng-disabled, ng-required, etc) are evaluated rather than interpolated
([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- `ng-bind-attr` directive removed
([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
- any app that depends on $sniffer service should use Modernizr instead
([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
<a name="v1.0.0rc2"></a>
# v1.0.0rc2 silence-absorption (2012-03-20)
## Features
- **$route:** when matching consider trailing slash as optional
([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6))
- **jqLite:** add .controller() method
([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
- **scope.$eval:** allow passing locals to the expression
([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
- **input[type=radio]:** allow the value attribute to be interpolated
([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
## Bug Fixes
- **$http:** don't send Content-Type header when no data
([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb),
[#749](https://github.com/angular/angular.js/issues/749))
- **$resource:** support escaping of ':' in resource url
([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
- **$compile:**
- don't touch static element attributes
([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
- merge interpolated css class when replacing an element
([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
- allow transclusion of root elements
([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
- **$log:** avoid console.log.apply calls in IE
([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca),
[#805](https://github.com/angular/angular.js/issues/805))
- **json:** added support for iso8061 timezone
([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
- **e2e runner:** fix typo that caused errors on IE8
([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b),
[#806](https://github.com/angular/angular.js/issues/806))
- **directives:**
- **select:** multiselect failes to update view on selection insert
([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
- **ngForm:** alias name||ngForm
([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
- **ngView:** publish the controller
([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
- **ngRepeat:** correct variable reference in error message
([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
- various doc fixes (some contributed by Daniel Zen)
<a name="1.0.0rc1"></a>
# 1.0.0rc1 moiré-vision (2012-03-13)
+65 -70
View File
@@ -2,7 +2,10 @@ require 'yaml'
include FileUtils
content = File.open('angularFiles.js', 'r') {|f| f.read }
files = eval(content.gsub(/\};(\s|\S)*/, '}').gsub(/angularFiles = /, '').gsub(/:/, '=>'));
files = eval(content.gsub(/\};(\s|\S)*/, '}').
gsub(/angularFiles = /, '').
gsub(/:/, '=>').
gsub(/\/\//, '#'));
BUILD_DIR = 'build'
@@ -35,37 +38,23 @@ end
desc 'Compile Scenario'
task :compile_scenario => :init do
deps = [
concat_file('angular-scenario.js', [
'lib/jquery/jquery.js',
'src/scenario/angular.prefix',
'src/ngScenario/angular.prefix',
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}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
f.write(gen_css('css/angular.css') + "\n")
f.write(gen_css('css/angular-scenario.css'))
end
'src/ngScenario/angular.suffix',
], gen_css('css/angular.css') + "\n" + gen_css('css/angular-scenario.css'))
end
desc 'Compile JSTD Scenario Adapter'
task :compile_jstd_scenario_adapter => :init do
deps = [
'src/jstd-scenario-adapter/angular.prefix',
'src/jstd-scenario-adapter/Adapter.js',
'src/jstd-scenario-adapter/angular.suffix',
]
concat = 'cat ' + deps.flatten.join(' ')
File.open(path_to('jstd-scenario-adapter.js'), 'w') do |f|
f.write(%x{#{concat}}.gsub('"NG_VERSION_FULL"', NG_VERSION.full))
end
concat_file('jstd-scenario-adapter.js', [
'src/ngScenario/jstd-scenario-adapter/angular.prefix',
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
'src/ngScenario/jstd-scenario-adapter/angular.suffix',
])
# TODO(vojta) use jstd configuration when implemented
# (instead of including jstd-adapter-config.js)
@@ -80,55 +69,28 @@ end
desc 'Compile JavaScript'
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
deps = [
'src/angular.prefix',
files['angularSrc'],
'src/angular.suffix',
]
concat_file('angular.js', [
'src/angular.prefix',
files['angularSrc'],
'src/angular.suffix',
], gen_css('css/angular.css', true))
File.open(path_to('angular.js'), 'w') do |f|
concat = 'cat ' + deps.flatten.join(' ')
FileUtils.cp_r 'src/ngLocale', path_to('i18n')
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/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')
File.open(path_to('angular-loader.js'), 'w') do |f|
concat = 'cat ' + [
concat_file('angular-loader.js', [
'src/loader.prefix',
'src/loader.js',
'src/loader.suffix'].flatten.join(' ')
'src/loader.suffix'])
content = %x{#{concat}}.
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
gsub(/^\s*['"]use strict['"];?\s*$/, '') # remove all file-specific strict mode flags
FileUtils.cp 'src/ngMock/angular-mocks.js', path_to('angular-mocks.js')
FileUtils.cp 'src/ngResource/resource.js', path_to('angular-resource.js')
FileUtils.cp 'src/ngCookies/cookies.js', path_to('angular-cookies.js')
f.write(content)
end
%x(java -jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to('angular-loader.js')} \
--js_output_file #{path_to('angular-loader.min.js')})
closure_compile('angular.js')
closure_compile('angular-cookies.js')
closure_compile('angular-loader.js')
closure_compile('angular-resource.js')
end
@@ -153,16 +115,20 @@ task :package => [:clean, :compile, :docs] do
FileUtils.rm_r(path_to('pkg'), :force => true)
FileUtils.mkdir_p(pkg_dir)
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular-loader.js'),
[ path_to('angular.js'),
path_to('angular.min.js'),
path_to('angular-loader.js'),
path_to('angular-loader.min.js'),
path_to('angular-mocks.js'),
path_to('angular-cookies.js'),
path_to('angular-cookies.min.js'),
path_to('angular-resource.js'),
path_to('angular-resource.min.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)$/, "-#{NG_VERSION.full}\\1")
dest = src.gsub(/^.*\//, '').gsub(/((\.min)?\.js)$/, "-#{NG_VERSION.full}\\1")
FileUtils.cp(src, pkg_dir + '/' + dest)
end
@@ -336,3 +302,32 @@ end
def path_to(filename)
return File.join(BUILD_DIR, *filename)
end
def closure_compile(filename)
puts "Compiling #{filename} ..."
%x(java -jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to(filename)} \
--js_output_file #{path_to(filename.gsub(/\.js$/, '.min.js'))})
end
def concat_file(filename, deps, footer='')
puts "Building #{filename} ..."
File.open(path_to(filename), 'w') do |f|
concat = 'cat ' + deps.flatten.join(' ')
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(footer)
end
end
+138 -113
View File
@@ -3,81 +3,102 @@ angularFiles = {
'src/Angular.js',
'src/loader.js',
'src/AngularPublic.js',
'src/JSON.js',
'src/Injector.js',
'src/Resource.js',
'src/jqLite.js',
'src/apis.js',
'src/service/anchorScroll.js',
'src/service/browser.js',
'src/service/cacheFactory.js',
'src/service/compiler.js',
'src/service/controller.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
'src/service/document.js',
'src/service/exceptionHandler.js',
'src/service/filter.js',
'src/service/filter/filter.js',
'src/service/filter/filters.js',
'src/service/filter/limitTo.js',
'src/service/filter/orderBy.js',
'src/service/interpolate.js',
'src/service/location.js',
'src/service/log.js',
'src/service/resource.js',
'src/service/parse.js',
'src/service/q.js',
'src/service/route.js',
'src/service/routeParams.js',
'src/service/scope.js',
'src/service/sanitize.js',
'src/service/sniffer.js',
'src/service/window.js',
'src/service/http.js',
'src/service/httpBackend.js',
'src/service/locale.js',
'src/directive/directives.js',
'src/directive/a.js',
'src/directive/booleanAttrDirs.js',
'src/directive/form.js',
'src/directive/input.js',
'src/directive/ngBind.js',
'src/directive/ngClass.js',
'src/directive/ngCloak.js',
'src/directive/ngController.js',
'src/directive/ngEventDirs.js',
'src/directive/ngInclude.js',
'src/directive/ngInit.js',
'src/directive/ngNonBindable.js',
'src/directive/ngPluralize.js',
'src/directive/ngRepeat.js',
'src/directive/ngShowHide.js',
'src/directive/ngStyle.js',
'src/directive/ngSwitch.js',
'src/directive/ngTransclude.js',
'src/directive/ngView.js',
'src/directive/script.js',
'src/directive/select.js',
'src/directive/style.js'
'src/auto/injector.js',
'src/ng/anchorScroll.js',
'src/ng/browser.js',
'src/ng/cacheFactory.js',
'src/ng/compiler.js',
'src/ng/controller.js',
'src/ng/defer.js',
'src/ng/document.js',
'src/ng/exceptionHandler.js',
'src/ng/interpolate.js',
'src/ng/location.js',
'src/ng/log.js',
'src/ng/parse.js',
'src/ng/q.js',
'src/ng/route.js',
'src/ng/routeParams.js',
'src/ng/rootScope.js',
'src/ng/sanitize.js',
'src/ng/sniffer.js',
'src/ng/window.js',
'src/ng/http.js',
'src/ng/httpBackend.js',
'src/ng/locale.js',
'src/ng/filter.js',
'src/ng/filter/filter.js',
'src/ng/filter/filters.js',
'src/ng/filter/limitTo.js',
'src/ng/filter/orderBy.js',
'src/ng/directive/directives.js',
'src/ng/directive/a.js',
'src/ng/directive/booleanAttrDirs.js',
'src/ng/directive/form.js',
'src/ng/directive/input.js',
'src/ng/directive/ngBind.js',
'src/ng/directive/ngClass.js',
'src/ng/directive/ngCloak.js',
'src/ng/directive/ngController.js',
'src/ng/directive/ngEventDirs.js',
'src/ng/directive/ngInclude.js',
'src/ng/directive/ngInit.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngPluralize.js',
'src/ng/directive/ngRepeat.js',
'src/ng/directive/ngShowHide.js',
'src/ng/directive/ngStyle.js',
'src/ng/directive/ngSwitch.js',
'src/ng/directive/ngTransclude.js',
'src/ng/directive/ngView.js',
'src/ng/directive/script.js',
'src/ng/directive/select.js',
'src/ng/directive/style.js'
],
'angularSrcModules': [
'src/ngCookies/cookies.js',
'src/ngResource/resource.js',
'src/ngMock/angular-mocks.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'
'src/ngScenario/Scenario.js',
'src/ngScenario/Application.js',
'src/ngScenario/Describe.js',
'src/ngScenario/Future.js',
'src/ngScenario/ObjectModel.js',
'src/ngScenario/Describe.js',
'src/ngScenario/Runner.js',
'src/ngScenario/SpecRunner.js',
'src/ngScenario/dsl.js',
'src/ngScenario/matchers.js',
'src/ngScenario/output/Html.js',
'src/ngScenario/output/Json.js',
'src/ngScenario/output/Xml.js',
'src/ngScenario/output/Object.js'
],
'angularTest': [
'test/testabilityPatch.js',
'test/matchers.js',
'test/ngScenario/*.js',
'test/ngScenario/output/*.js',
'test/ngScenario/jstd-scenario-adapter/*.js',
'test/*.js',
'test/auto/*.js',
'test/ng/*.js',
'test/ng/directive/*.js',
'test/ng/filter/*.js',
'test/ngCookies/*.js',
'test/ngResource/*.js',
'test/ngMock/*.js'
],
'jstd': [
@@ -86,28 +107,19 @@ angularFiles = {
'lib/jquery/jquery.js',
'test/jquery_remove.js',
'@angularSrc',
'src/publishExternalApis.js',
'@angularSrcModules',
'@angularScenario',
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
'@angularTest',
'example/personalLog/*.js',
'test/testabilityPatch.js',
'test/matchers.js',
'src/scenario/Scenario.js',
'src/scenario/output/*.js',
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/service/filter/*.js',
'test/directive/*.js',
'example/personalLog/test/*.js'
],
'jstdExclude': [
'test/jquery_alias.js',
'src/angular-bootstrap.js',
'src/scenario/angular-bootstrap.js'
'src/ngScenario/angular-bootstrap.js'
],
'jstdScenario': [
@@ -117,28 +129,33 @@ angularFiles = {
'build/docs/docs-scenario.js'
],
'jstdMocks': [
"jstdModules": [
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'build/angular.js',
'src/angular-mocks.js',
'src/ngMock/angular-mocks.js',
'src/ngCookies/cookies.js',
'src/ngResource/resource.js',
'test/matchers.js',
'test/angular-mocksSpec.js'
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngResource/*.js'
],
'jstdPerf': [
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'angularSrc',
'src/angular-mocks.js',
'@angularSrc',
'@angularSrcModules',
'src/ngMock/angular-mocks.js',
'perf/data/*.js',
'perf/testUtils.js',
'perf/*.js'
],
'jstdPerfExclude': [
'src/angular-bootstrap.js',
'src/scenario/angular-bootstrap.js'
'src/ng/angular-bootstrap.js',
'src/ngScenario/angular-bootstrap.js'
],
'jstdJquery': [
@@ -147,41 +164,49 @@ angularFiles = {
'lib/jquery/jquery.js',
'test/jquery_alias.js',
'@angularSrc',
'src/publishExternalApis.js',
'@angularSrcModules',
'@angularScenario',
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
'@angularTest',
'example/personalLog/*.js',
'test/testabilityPatch.js',
'test/matchers.js',
'src/scenario/Scenario.js',
'src/scenario/output/*.js',
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/directive/*.js',
'example/personalLog/test/*.js'
],
'jstdJqueryExclude': [
'src/angular-bootstrap.js',
'src/scenario/angular-bootstrap.js',
'src/ngScenario/angular-bootstrap.js',
'test/jquery_remove.js'
]
};
// Execute only in slim-jim
if (typeof JASMINE_ADAPTER !== 'undefined') {
// SlimJim config
files = [JASMINE, JASMINE_ADAPTER];
angularFiles.jstd.forEach(function(pattern) {
// replace angular source
if (pattern === '@angularSrc') files = files.concat(angularFiles.angularSrc);
// ignore jstd and jasmine files
else if (!/jstd|jasmine/.test(pattern)) files.push(pattern);
// Testacular config
var mergedFiles = [];
angularFiles.jstd.forEach(function(file) {
// replace @ref
var match = file.match(/^\@(.*)/);
if (match) {
var deps = angularFiles[match[1]];
if (!deps) {
console.log('No dependency:' + file)
}
mergedFiles = mergedFiles.concat(deps);
} else {
mergedFiles.push(file);
}
});
files = [JASMINE, JASMINE_ADAPTER];
mergedFiles.forEach(function(file){
if (/jstd|jasmine/.test(file)) return;
files.push(file);
});
exclude = angularFiles.jstdExclude;
autoWatch = true;
Executable
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env node
// TODO(vojta): pre-commit hook for validating messages
// TODO(vojta): report errors, currently Q silence everything which really sucks
var child = require('child_process');
var fs = require('fs');
var util = require('util');
var q = require('qq');
var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n';
var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
var EMPTY_COMPONENT = '$$';
var MAX_SUBJECT_LENGTH = 80;
var warn = function() {
console.log('WARNING:', util.format.apply(null, arguments));
};
var parseRawCommit = function(raw) {
if (!raw) return null;
var lines = raw.split('\n');
var msg = {}, match;
msg.hash = lines.shift();
msg.subject = lines.shift();
msg.closes = [];
msg.breaks = [];
lines.forEach(function(line) {
match = line.match(/Closes\s#(\d+)/);
if (match) msg.closes.push(parseInt(match[1]));
match = line.match(/Breaks\s(.*)/);
if (match) msg.breaks.push(match[1]);
});
msg.body = lines.join('\n');
match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
if (!match || !match[1] || !match[3]) {
warn('Incorrect message: %s %s', msg.hash, msg.subject);
return null;
}
if (match[3].length > MAX_SUBJECT_LENGTH) {
warn('Too long subject: %s %s', msg.hash, msg.subject);
match[3] = match[3].substr(0, MAX_SUBJECT_LENGTH);
}
msg.type = match[1];
msg.component = match[2];
msg.subject = match[3];
return msg;
};
var linkToIssue = function(issue) {
return util.format(LINK_ISSUE, issue, issue);
};
var linkToCommit = function(hash) {
return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
};
var currentDate = function() {
var now = new Date();
var pad = function(i) {
return ('0' + i).substr(-2);
};
return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
};
var printSection = function(stream, title, section) {
var NESTED = true;
var components = Object.getOwnPropertyNames(section).sort();
if (!components.length) return;
stream.write(util.format('\n## %s\n\n', title));
components.forEach(function(name) {
var prefix = '-';
if (name !== EMPTY_COMPONENT) {
if (NESTED) {
stream.write(util.format('- **%s:**\n', name));
prefix = ' -';
} else {
prefix = util.format('- **%s:**', name);
}
}
section[name].forEach(function(commit) {
stream.write(util.format('%s %s (%s', prefix, commit.subject, linkToCommit(commit.hash)));
if (commit.closes.length) {
stream.write(', closes ' + commit.closes.map(linkToIssue).join(', '));
}
stream.write(')\n');
});
});
stream.write('\n');
};
var readGitLog = function(grep, from) {
var deffered = q.defer();
// TODO(vojta): if it's slow, use spawn and stream it instead
child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
var commits = [];
stdout.split('\n==END==\n').forEach(function(rawCommit) {
var commit = parseRawCommit(rawCommit);
if (commit) commits.push(commit);
});
deffered.resolve(commits);
});
return deffered.promise;
};
var writeChangelog = function(stream, commits, version) {
var sections = {
fix: {},
feat: {},
breaks: {}
};
sections.breaks[EMPTY_COMPONENT] = [];
commits.forEach(function(commit) {
var section = sections[commit.type];
var component = commit.component || EMPTY_COMPONENT;
if (section) {
section[component] = section[component] || [];
section[component].push(commit);
}
commit.breaks.forEach(function(breakMsg) {
sections.breaks[EMPTY_COMPONENT].push({
subject: breakMsg,
hash: commit.hash,
closes: []
});
});
});
stream.write(util.format(HEADER_TPL, version, version, currentDate()));
printSection(stream, 'Bug Fixes', sections.fix);
printSection(stream, 'Features', sections.feat);
printSection(stream, 'Breaking Changes', sections.breaks);
}
var getPreviousTag = function() {
var deffered = q.defer();
child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
if (code) deffered.reject('Cannot get the previous tag.');
else deffered.resolve(stdout.replace('\n', ''));
});
return deffered.promise;
};
var generate = function(version, file) {
getPreviousTag().then(function(tag) {
console.log('Reading git log since', tag);
readGitLog('^fix|^feat|Breaks', tag).then(function(commits) {
console.log('Parsed', commits.length, 'commits');
console.log('Generating changelog to', file || 'stdout', '(', version, ')');
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
});
});
};
// publish for testing
exports.parseRawCommit = parseRawCommit;
// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
generate(process.argv[2], process.argv[3]);
}
+43
View File
@@ -0,0 +1,43 @@
describe('changelog.js', function() {
var ch = require('./changelog');
describe('parseRawCommit', function() {
it('should parse raw commit', function() {
var msg = ch.parseRawCommit(
'9b1aff905b638aa274a5fc8f88662df446d374bd\n' +
'feat(scope): broadcast $destroy event on scope destruction\n' +
'perf testing shows that in chrome this change adds 5-15% overhead\n' +
'when destroying 10k nested scopes where each scope has a $destroy listener\n');
expect(msg.type).toBe('feat');
expect(msg.hash).toBe('9b1aff905b638aa274a5fc8f88662df446d374bd');
expect(msg.subject).toBe('broadcast $destroy event on scope destruction');
expect(msg.body).toBe('perf testing shows that in chrome this change adds 5-15% overhead\n' +
'when destroying 10k nested scopes where each scope has a $destroy listener\n')
expect(msg.component).toBe('scope');
});
it('should parse closed issues', function() {
var msg = ch.parseRawCommit(
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
'feat(ng-list): Allow custom separator\n' +
'bla bla bla\n\n' +
'Closes #123\nCloses #25\n');
expect(msg.closes).toEqual([123, 25]);
});
it('should parse breaking changes', function() {
var msg = ch.parseRawCommit(
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
'feat(ng-list): Allow custom separator\n' +
'bla bla bla\n\n' +
'Breaks first breaking change\nsomething else\n' +
'Breaks another breaking change\n');
expect(msg.breaks).toEqual(['first breaking change', 'another breaking change']);
});
});
});
+80
View File
@@ -0,0 +1,80 @@
<a name="v1.0.0rc3"></a>
# v1.0.0rc3 (2012-03-27)
## Bug Fixes
- **$compile:**
- create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
- don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
- Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
- **$http:**
- don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
- **$log:**
- avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
- **$resource:**
- support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
- **compiler:**
- allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
- **e2e runner:**
- fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
- **forEach:**
- should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
- **forms:**
- Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
- Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
- **init:**
- use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
- **json:**
- added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
- **matchers.toHaveClass:**
- Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
- **ng-switch:**
- properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
- **ngDocSpec:**
- fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
- **ngForm:**
- alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
- **ngRepeat:**
- correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
- **ngView:**
- controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
- **q:**
- resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
- **select:**
- multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
## Features
- **$compile:**
- do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- **$controller:**
- support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
- **$route:**
- when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
- **assertArgFn:**
- should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
- **http:**
- added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
- **injector:**
- infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
- **input.radio:**
- Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
- **jqLite:**
- make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
- add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
- **ngValue:**
- allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
- **scope:**
- broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
- **scope.$eval:**
- Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
## Breaking Changes
- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
+2 -2
View File
@@ -85,8 +85,8 @@ detection, and preventing invalid form submission.
<input type="text" ng-model="contact.value" required/>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<button ng-click="cancel()" ng-disabled="{{isCancelDisabled()}}">Cancel</button>
<button ng-click="save()" ng-disabled="{{isSaveDisabled()}}">Save</button>
<button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button>
<button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button>
</form>
<hr/>
+11 -11
View File
@@ -20,7 +20,7 @@ allow a user to enter data.
$scope.zip = /^\d\d\d\d\d$/;
$scope.addContact = function() {
$scope.user.contacts.push({type:'', value:''});
$scope.user.contacts.push({type:'email', value:''});
};
$scope.removeContact = function(contact) {
@@ -34,16 +34,16 @@ allow a user to enter data.
</script>
<div ng-controller="FormController" class="example">
<label>Name:</label><br/>
<input type="text" ng-model="user.name" required/> <br/><br/>
<label>Name:</label><br>
<input type="text" ng-model="user.name" required/> <br><br>
<label>Address:</label><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>
<label>Address:</label><br>
<input type="text" ng-model="user.address.line1" ng-model-instant size="33" required> <br>
<input type="text" ng-model="user.address.city" ng-model-instant size="12" required>,
<input type="text" ng-model="user.address.state" ng-model-instant
ng-pattern="state" size="2" required>
<input type="text" ng-model="user.address.zip" size="5"
ng-pattern="zip" required><br/><br/>
ng-pattern="zip" required><br><br>
<label>Phone:</label>
[ <a href="" ng-click="addContact()">add</a> ]
@@ -54,12 +54,12 @@ allow a user to enter data.
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required/>
<input type="text" ng-model="contact.value" ng-model-instant required/>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<hr/>
Debug View:
<pre>user={{user}}</pre>
<pre>user={{user | json}}</pre>
</div>
</doc:source>
@@ -2,8 +2,7 @@
@name Developer Guide: Initializing Angular: Automatic Initialization
@description
Angular initializes automatically when you load the angular script into your page that contains an element
with `ng-app` directive:
For Angular to manage the DOM for your application, it needs to compile some or all of an HTML page. Angular does this initialization automatically when you load the angular.js script into your page and insert an `ng-app` directive (attribute) into one of the page's elements. For example, we can tell Angular to initialize the entire document:
<pre>
<!doctype html>
@@ -17,30 +16,36 @@ with `ng-app` directive:
</html>
</pre>
From a high-level view, this is what happens during angular's automatic initialization process:
You can also tell Angular to manage only a portion of a page. You would want to do this if you are using some other framework to manage other parts of the page. You do this by placing the `ng-app` directive on one or more container elements in the document. For example:
1. The browser loads the page, and then runs the angular script. Angular waits for the
`DOMContentLoaded` (or 'Load') event to attempt to bootstrap.
<pre>
<div ng-app>
I can add: {{ 1+2 }}
</div>
</pre>
2. Angular looks for the `ng-app` directive. If found it then proceeds to compile the DOM element and its children.
Optionally the `ng-app` may specify a {@link api/angular.module module} to load before the compilation. For details on
how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
You can also ask `ng-app` to load additional {@link api/angular.module modules} containing services, directives or filers that you'll use on the page.
<pre>
<div ng-app="AwesomeModule">
...
</div>
</pre
## Initialization Options
From a high-level, here's what Angular does during the initialization process:
The reason why `ng-app` 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 set the `ng-app` on the root element of this portion.
1. The browser loads the page, and then runs the Angular script. Angular then waits for the
`DOMContentLoaded` (or 'Load') event to attempt to initialize.
## Global Angular Object
2. Angular looks for the `ng-app` directive. If found it compilies the DOM element containing `ng-app` and its children.
The angular script creates a single global variable `angular` in the global namespace. All angular
APIs are bound to fields of this global object.
3. Angular creates a global variable `angular` and binds all Angular APIs to this object's fields.
## Related Topics
* {@link dev_guide.compiler Angular HTML Compiler}
* {@link dev_guide.bootstrap Initializing Angular}
* {@link dev_guide.bootstrap.manual_bootstrap Manual Initialization}
@@ -2,13 +2,26 @@
@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.
In the vast majority of cases you'll want to let Angular handle initialization automatically.
If, however, you need to delay Angular from managing the page right after the DOMContentLoaded
event fires, you'll need to control this initialization manually.
The best way to get started with manual bootstrapping is to look at the what happens when you use
{@link api/angular.module.ng.$compileProvider.directive.ng-app ng-app}, by showing each step of the process
explicitly.
To initialize Angular -- after you've done your own special-purpose initialization -- just call
the {@link api/angular.bootstrap bootstrap()} function with the HTML container node that you want
Angular to manage. In automatic initialization you'd do this by adding the `ng-app` attribute to
the same node. Now, you won't use `ng-app` anywhere in your document.
To show the contrast of manual vs. automatic initialization, this automatic method:
<pre>
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular.js"></script>
...
</pre
is the same as this manual method:
<pre>
<!doctype html>
@@ -21,19 +34,9 @@ explicitly.
});
</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. Call {@link api/angular.bootstrap} to {@link dev_guide.compiler compile} the template into
an executable, bi-directionally bound application.
## Related Topics
@@ -6,18 +6,17 @@ The most common place to use dependency injection in angular applications is in
dev_guide.mvc.understanding_controller controllers}. Here is a simple example:
<pre>
function MyController($route){
// configure the route service
$route.when(...);
function MyController($location){
// do stuff with the $location service
}
MyController.$inject = ['$route'];
MyController.$inject = ['$location'];
</pre>
In this example, the `MyController` constructor function takes one argument, the {@link
api/angular.module.ng.$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:
api/angular.module.ng.$location $location} service. Angular is then responsible for supplying the
instance of `$location` to the controller when the constructor is instantiated. There are two ways
to cause controller instantiation by configuring routes with the `$location` service, or by
referencing the controller from the HTML template, as follows:
<pre>
<!doctype html>
@@ -35,7 +34,7 @@ we have to supply this information to angular in the form of an additional prope
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
<pre>
MyController.$inject = ['$route'];
MyController.$inject = ['$location'];
</pre>
The information in `$inject` is then used by the {@link api/angular.injector injector} to call the
@@ -206,19 +206,17 @@ 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:
Although there are many ways to test a controller, one of the best conventions, shown below,
involves injecting the `$rootScope` and `$controller`
Controller Function:
<pre>
function myController() {
this.spices = [{"name":"pasilla", "spiciness":"mild"},
function myController($scope) {
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
this.spice = "habanero";
$scope.spice = "habanero";
}
</pre>
@@ -227,27 +225,51 @@ Controller Test:
describe('myController function', function() {
describe('myController', function() {
var ctrl;
var scope;
beforeEach(function() {
ctrl = new myController();
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
var ctrl = $controller(myController, {$scope: scope});
}));
it('should create "spices" model with 3 spices', function() {
expect(ctrl.spices.length).toBe(3);
expect(scope.spices.length).toBe(3);
});
it('should set the default value of spice', function() {
expect(ctrl.spice).toBe('habanero');
expect(scope.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.
If you need to test a nested controller one needs to create the same scope hierarchy
in your test as exist in the DOM.
<pre>
describe('state', function() {
var mainScope, childScope, babyScope;
beforeEach(inject(function($rootScope, $controller) {
mainScope = $rootScope.$new();
var mainCtrl = $controller(MainCtrl, {$scope: mainScope});
childScope = mainScope.$new();
var childCtrl = $controller(ChildCtrl, {$scope: childScope});
babyScope = $rootScope.$new();
var babyCtrl = $controller(BabyCtrl, {$scope: babyScope});
}));
it('should have over and selected', function() {
expect(mainScope.timeOfDay).toBe('morning');
expect(mainScope.name).toBe('Nikki');
expect(childScope.timeOfDay).toBe('morning');
expect(childScope.name).toBe('Mattie');
expect(babyScope.timeOfDay).toBe('evening');
expect(babyScope.name).toBe('Gingerbreak Baby');
});
});
</pre>
## Related Topics
@@ -372,13 +372,13 @@ In this examples we use `<base href="/base/index.html" />`
<div 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 />
<div ng-address-bar browser="html5"></div><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>
@@ -386,13 +386,13 @@ In this examples we use `<base href="/base/index.html" />`
<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 />
<div ng-address-bar browser="hashbang"></div><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>
@@ -417,7 +417,6 @@ In this examples we use `<base href="/base/index.html" />`
return baseHref;
};
this.hover = angular.noop;
this.notifyWhenOutstandingRequests = angular.noop;
}
@@ -426,20 +425,19 @@ In this examples we use `<base href="/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 Html5Cntl($scope, $location) {
$scope.$location = $location;
}
function HashbangCntl($location) {
this.$location = $location;
function HashbangCntl($scope, $location) {
$scope.$location = $location;
}
function initEnv(name) {
var root = angular.element(document.getElementById(name + '-mode'));
angular.bootstrap(root, [function($compileProvider, $locationProvider, $provide){
$locationProvider.html5Mode = true;
$locationProvider.hashPrefix = '!';
$locationProvider.html5Mode(true).hashPrefix('!');
$provide.value('$browser', browsers[name]);
$provide.value('$document', root);
$provide.value('$sniffer', {history: name == 'html5'});
@@ -92,8 +92,8 @@ 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 oldXHR = global.xhr;
global.xhr = function mockXHR() {};
var myClass = new MyClass();
myClass.doWork();
// assert that mockXHR got called with the right arguments
@@ -126,12 +126,12 @@ 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 oldServiceLocator = global.serviceLocator;
global.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
global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
</pre>
+3 -3
View File
@@ -190,7 +190,7 @@ modules require it.
Modules are a way of managing $injector configuration, and have nothing to do with loading of
scripts into a VM. There are existing projects which deal with script loading, which may be used
with Angular. Because modules do nothing at load time they can be loaded into the VM in any order
and thus script loaders can take advantage of this property and paralyze the loading process.
and thus script loaders can take advantage of this property and parallelize the loading process.
# Unit Testing
@@ -232,7 +232,7 @@ describe('myApp', function() {
$provide.value('$window', {
alert: jasmine.createSpy('alert')
});
});
}));
// The inject() will create the injector and inject the greet and
// $window into the tests. The test need not concern itself with
@@ -251,7 +251,7 @@ describe('myApp', function() {
});
inject(function(greet) {
greet('World');
expect(alertSpy).toHaveBeenCalledWith('World');
expect(alertSpy).toHaveBeenCalledWith('Hello World!');
});
});
});
+4 -4
View File
@@ -65,15 +65,15 @@ This example demonstrates angular's two-way data binding:
<doc:example>
<doc:source>
Your name: <input type="text" ng-model="yourname" value="World"/>
<hr/>
Hello {{yourname}}!
Your name: <input type="text" ng-model="yourname" ng-model-instant placeholder="World">
<hr>
Hello {{yourname || 'World'}}!
</doc:source>
</doc:example>
After the refresh, the page should look something like this:
<img class="left" src="img/helloworld_2way.png" border="1" />
<img class="left" src="img/helloworld_2way.png" border="1" >
These are some of the important points to note from this example:
+1 -1
View File
@@ -35,7 +35,7 @@ describe('ngdoc', function() {
var d2 = new Doc('@name a.b.ng-c').parse();
var d3 = new Doc('@name some text: more text').parse();
expect(ngdoc.metadata([d1])[0].shortName).toEqual('c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng-c');
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
+7
View File
@@ -677,8 +677,15 @@ var KEYWORD_PRIORITY = {
'.angular.module.ngMock': 8,
'.dev_guide.overview': 1,
'.dev_guide.bootstrap': 2,
'.dev_guide.bootstrap.auto_bootstrap': 1,
'.dev_guide.bootstrap.manual_bootstrap': 2,
'.dev_guide.mvc': 3,
'.dev_guide.mvc.understanding_model': 1,
'.dev_guide.mvc.understanding_controller': 2,
'.dev_guide.mvc.understanding_view': 3,
'.dev_guide.scopes': 4,
'.dev_guide.scopes.understanding_scopes': 1,
'.dev_guide.scopes.internals': 2,
'.dev_guide.compiler': 5,
'.dev_guide.templates': 6,
'.dev_guide.services': 7,
+2 -2
View File
@@ -2,11 +2,11 @@ angular.module('ngdocs.directives', [], function($compileProvider) {
var angularJsUrl;
var scripts = document.getElementsByTagName("script");
var angularJsRegex = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
var angularJsRegex = /^(|.*\/)angular(-\d.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
for(var j = 0; j < scripts.length; j++) {
var src = scripts[j].src;
if (src && src.match(angularJsRegex)) {
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
angularJsUrl = src.replace(/docs(-next)?\.angularjs\.org/, 'code.angularjs.org');
continue;
}
}
+2 -2
View File
@@ -104,7 +104,7 @@ function DocsController(scope, $location, $window, $cookies, $filter) {
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs';
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs-next.angularjs.org' + currentPageId;
@@ -146,7 +146,7 @@ function TutorialInstructionsCtrl($cookieStore) {
};
}
angular.module('ngdocs', ['ngdocs.directives'], function($locationProvider, $filterProvider, $compileProvider) {
angular.module('ngdocs', ['ngdocs.directives', 'ngResource', 'ngCookies'], function($locationProvider, $filterProvider, $compileProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$filterProvider.register('title', function(){
+7 -2
View File
@@ -18,7 +18,6 @@
baseUrl = location.href.replace(rUrl, indexFile),
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
angularPath = debug ? '../angular.js' : '../angular.min.js',
headEl = document.getElementsByTagName('head')[0],
sync = true;
@@ -28,10 +27,16 @@
type: 'text/css'});
addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
if (jQuery) addTag('script', {src: 'jquery.min.js'});
addTag('script', {src: angularPath}, sync);
addTag('script', {src: path('angular.js')}, sync);
addTag('script', {src: path('angular-resource.js') }, sync);
addTag('script', {src: path('angular-cookies.js') }, sync);
addTag('script', {src: 'docs-combined.js'}, sync);
addTag('script', {src: 'docs-keywords.js'}, sync);
function path(name) {
return '../' + name.replace(/\.js$/, debug ? '.js' : '.min.js');
}
function addTag(name, attributes, sync) {
var el = document.createElement(name),
attrName;
+15 -12
View File
@@ -1,28 +1,31 @@
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng:app>
<html ng-app>
<head>
<title>Personal Log</title>
<script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
<script type="text/javascript" src="personalLog.js"></script>
<script src="../../src/loader.js"></script>
<script>
setupModuleLoader(window);
</script>
<script src="personalLog.js"></script>
<script src="../../src/angular-bootstrap.js"></script>
<script src="../../src/ngCookies/cookies.js"></script>
</head>
<!-- TODO: we need to expose $root so that we can delete cookies in the scenario runner, there
must be a better way to do this -->
<body ng:controller="example.personalLog.LogCtrl">
<body ng-controller="LogCtrl">
<form action="" ng:submit="addLog(newMsg)">
<input type="text" ng:model="newMsg" />
<input type="submit" value="add" />
<input type="button" value="remove all" ng:click="rmLogs()" />
<form action="" ng-submit="addLog(newMsg)">
<input type="text" ng-model="newMsg">
<input type="submit" value="add">
<input type="button" value="remove all" ng-click="rmLogs()">
</form>
<hr/>
<h2>Logs:</h2>
<ul>
<li ng:repeat="log in logs | orderBy:'-at'">
<li ng-repeat="log in logs | orderBy:'-at'">
{{log.at | date:'yy-MM-dd HH:mm'}} {{log.msg}}
[<a href="" ng:click="rmLog(log)">x</a>]
[<a href="" ng-click="rmLog(log)">x</a>]
</li>
</ul>
+5 -15
View File
@@ -5,28 +5,23 @@
* - testability of controllers
* - dependency injection for controllers via $inject and constructor function
* - $cookieStore for persistent cookie-backed storage
* - simple templating constructs such as ng:repeat and {{}}
* - simple templating constructs such as ng-repeat and {{}}
* - date filter
* - and binding onSubmit and onClick events to angular expressions
* @author Igor Minar
*/
/** @namespace the 'example' namespace */
var example = example || {};
/** @namespace namespace of the personal log app */
example.personalLog = {};
//name space isolating closure
(function() {
var app = angular.module('personalLog', ['ngCookies']);
var LOGS = 'logs';
/**
* The controller for the personal log app.
*/
function LogCtrl($cookieStore, $scope) {
app.controller('LogCtrl', ['$cookieStore', '$scope', function LogCtrl($cookieStore, $scope) {
var logs = $scope.logs = $cookieStore.get(LOGS) || []; //main model
@@ -72,11 +67,6 @@ function LogCtrl($cookieStore, $scope) {
logs.splice(0, logs.length);
$cookieStore.remove(LOGS);
};
}
}]);
//inject
LogCtrl.$inject = ['$cookieStore', '$scope'];
//export
example.personalLog.LogCtrl = LogCtrl;
})();
+20 -19
View File
@@ -1,12 +1,13 @@
describe('example.personalLog.LogCtrl', function() {
var logScope;
beforeEach(function() {
var injector = angular.injector(['ng', 'ngMock']);
logScope = injector.get('$rootScope');
logScope.$cookies = injector.get('$cookies');
injector.instantiate(example.personalLog.LogCtrl, {$scope: logScope});
});
beforeEach(module('personalLog'));
beforeEach(inject(function($rootScope, $controller) {
logScope = $rootScope.$new();
$controller('LogCtrl', {$scope: logScope});
}));
it('should initialize notes with an empty array', function() {
@@ -43,11 +44,11 @@ describe('example.personalLog.LogCtrl', function() {
});
it('should store logs in the logs cookie', function() {
expect(logScope.$cookies.logs).not.toBeDefined();
it('should store logs in the logs cookie', inject(function($cookies) {
expect($cookies.logs).not.toBeDefined();
logScope.addLog('first log message');
expect(logScope.$cookies.logs).toBeTruthy();
});
expect($cookies.logs).toBeTruthy();
}));
it('should do nothing if newMsg is empty', function() {
@@ -79,17 +80,17 @@ describe('example.personalLog.LogCtrl', function() {
});
it('should update cookies when a log is deleted', function() {
expect(logScope.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
it('should update cookies when a log is deleted', inject(function($cookies) {
expect($cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){3}\]/);
logScope.rmLog(logScope.logs[1]);
expect(logScope.$cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
expect($cookies.logs).toMatch(/\[\{.*?\}(,\{.*?\}){2}\]/);
logScope.rmLog(logScope.logs[0]);
logScope.rmLog(logScope.logs[0]);
logScope.rmLog(logScope.logs[0]);
expect(logScope.$cookies.logs).toMatch(/\[\]/);
});
expect($cookies.logs).toMatch(/\[\]/);
}));
});
@@ -110,10 +111,10 @@ describe('example.personalLog.LogCtrl', function() {
});
it('should remove logs cookie', function() {
expect(logScope.$cookies.logs).toBeTruthy();
it('should remove logs cookie', inject(function($cookies) {
expect($cookies.logs).toBeTruthy();
logScope.rmLogs();
expect(logScope.$cookies.logs).not.toBeDefined();
});
expect($cookies.logs).not.toBeDefined();
}));
});
});
+17 -9
View File
@@ -1,22 +1,30 @@
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng:app>
<html ng-app="example">
<head>
<title>angular dev sandbox</title>
<script src="../src/angular-bootstrap.js"></script>
<script src="../src/loader.js"></script>
<script>
angular.module.ng('routeConfig', function($route) {
$route.when('/view1', {controller: MyCtrl, template: 'view1.html'});
$route.when('/view2', {controller: MyCtrl, template: 'view2.html'});
setupModuleLoader(window);
angular.module('example', [], function($routeProvider) {
$routeProvider.when('/view1', {controller: MyCtrl, template: 'view1.html'});
$routeProvider.when('/view2', {controller: MyCtrl, template: 'view2.html'});
function MyCtrl() {};
}, {$inject: ['$route']});
function MyCtrl($location, $scope) {
$scope.url = function() {
return $location.url();
}
};
});
</script>
<script src="../src/angular-bootstrap.js"></script>
</head>
<body ng:init="$service('$window').$root = this">
<body>
<p>
<a href="#/view1">view1</a> | <a href="#/view2">view2</a> | <a href="#">blank</a>
</p>
view: <ng:view></ng:view>
<hr>
<div ng-view></div>
</body>
</html>
+1 -1
View File
@@ -1,2 +1,2 @@
view1<br>
location: {{$service('$location').href}}
location: {{url()}}
+1 -1
View File
@@ -1,2 +1,2 @@
view2<br/>
location: {{$service('$location').href}}<br/>
location: {{url()}}<br/>
+4 -2
View File
@@ -16,7 +16,7 @@ fs.readFile('angularFiles.js', function(err, data) {
fs.writeFile('./jsTestDriver.conf', prefix + combine(angularFiles.jstd,
angularFiles.jstdExclude));
fs.writeFile('./jsTestDriver-mocks.conf', prefix + combine(angularFiles.jstdMocks));
fs.writeFile('./jsTestDriver-modules.conf', prefix + combine(angularFiles.jstdModules));
fs.writeFile('./jsTestDriver-scenario.conf', prefixScenario +
combine(angularFiles.jstdScenario) +
@@ -40,6 +40,8 @@ function combine(load, exclude) {
if (exclude) fileList += ('\n\nexclude:\n- ' + exclude.join('\n- '));
//Replace placeholders for src list before returning
return fileList.replace(/@angularSrc/g, angularSrc);
return fileList.replace(/@(.*)/g, function(all, alias) {
return angularFiles[alias].join('\n- ');
});
}
+32 -32
View File
@@ -8,7 +8,7 @@ describe("localized filters", function() {
expect(binding('input | date:"medium"')).toBe('03/06/1977 18:07:23');
expect(binding('input | date:"longDate"')).toBe("3 de junio de 1977");
expect(binding('input | number')).toBe('234.234.443.432');
expect(binding('input | currency')).toBe('€&nbsp;234.234.443.432,00');
expect(binding('input | currency')).toBe('€\u00a0234.234.443.432,00');
});
});
@@ -20,8 +20,8 @@ describe("localized filters", function() {
it('should check filters for cs locale', function() {
expect(binding('input | date:"medium"')).toBe('3.6.1977 18:07:23');
expect(binding('input | date:"longDate"')).toBe("3. června 1977");
expect(binding('input | number')).toBe('234&nbsp;234&nbsp;443&nbsp;432');
expect(binding('input | currency')).toBe('234&nbsp;234&nbsp;443&nbsp;432,00&nbsp;K\u010d');
expect(binding('input | number')).toBe('234\u00a0234\u00a0443\u00a0432');
expect(binding('input | currency')).toBe('234\u00a0234\u00a0443\u00a0432,00\u00a0K\u010d');
});
});
@@ -34,7 +34,7 @@ describe("localized filters", function() {
expect(binding('input | date:"medium"')).toBe('03.06.1977 18:07:23');
expect(binding('input | date:"longDate"')).toBe("3. Juni 1977");
expect(binding('input | number')).toBe('234.234.443.432');
expect(binding('input | currency')).toBe('234.234.443.432,00&nbsp;€');
expect(binding('input | currency')).toBe('234.234.443.432,00\u00a0€');
});
});
@@ -53,42 +53,42 @@ describe("localized filters", function() {
describe('ng:pluralize for en locale', function() {
it('should show pluralized strings', function() {
expect(element('.ng-pluralize:first').html()).toBe('You have one email!');
expect(element('ng-pluralize:first').html()).toBe('You have one email!');
input('plInput').enter('0');
expect(element('.ng-pluralize:first').html()).toBe('You have no email!');
expect(element('ng-pluralize:first').html()).toBe('You have no email!');
input('plInput').enter('3');
expect(element('.ng-pluralize:first').html()).toBe('You have 3 emails!');
expect(element('ng-pluralize:first').html()).toBe('You have 3 emails!');
});
it('should show pluralized strings with offsets', function() {
expect(element('.ng-pluralize:last').html()).toBe('Shanjian is viewing!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian is viewing!');
input('plInput2').enter('0');
expect(element('.ng-pluralize:last').html()).toBe('Nobody is viewing!');
expect(element('ng-pluralize:last').html()).toBe('Nobody is viewing!');
input('plInput2').enter('2');
expect(element('.ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
input('plInput2').enter('3');
expect(element('.ng-pluralize:last').html()).
expect(element('ng-pluralize:last').html()).
toBe('Shanjian, Di and one other person are viewing!');
input('plInput2').enter('4');
expect(element('.ng-pluralize:last').html()).
expect(element('ng-pluralize:last').html()).
toBe('Shanjian, Di and 2 other people are viewing!');
});
it('should show pluralized strings with correct data-binding', function() {
input('plInput2').enter('2');
expect(element('.ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian and Di are viewing!');
input('person1').enter('Igor');
expect(element('.ng-pluralize:last').html()).toBe('Igor and Di are viewing!');
expect(element('ng-pluralize:last').html()).toBe('Igor and Di are viewing!');
input('person2').enter('Vojta');
expect(element('.ng-pluralize:last').html()).toBe('Igor and Vojta are viewing!');
expect(element('ng-pluralize:last').html()).toBe('Igor and Vojta are viewing!');
});
})
});
@@ -101,26 +101,26 @@ describe("localized filters", function() {
it('should check filters for sk locale', function() {
expect(binding('input | date:"medium"')).toBe('3.6.1977 18:07:23');
expect(binding('input | date:"longDate"')).toBe("3. júna 1977");
expect(binding('input | number')).toBe('234&nbsp;234&nbsp;443&nbsp;432');
expect(binding('input | currency')).toBe('234&nbsp;234&nbsp;443&nbsp;432,00&nbsp;Sk');
expect(binding('input | number')).toBe('234\u00a0234\u00a0443\u00a0432');
expect(binding('input | currency')).toBe('234\u00a0234\u00a0443\u00a0432,00\u00a0Sk');
});
describe('ng:pluralize for sk locale', function() {
it('should show pluralized strings', function() {
expect(element('.ng-pluralize').html()).toBe('Mas jeden email!');
expect(element('ng-pluralize').html()).toBe('Mas jeden email!');
input('plInput').enter('0');
expect(element('.ng-pluralize:first').html()).toBe('Mas 0 emailov!');
expect(element('ng-pluralize:first').html()).toBe('Mas 0 emailov!');
input('plInput').enter('3');
expect(element('.ng-pluralize:first').html()).toBe('Mas 3 emaily!');
expect(element('ng-pluralize:first').html()).toBe('Mas 3 emaily!');
input('plInput').enter('4');
expect(element('.ng-pluralize:first').html()).toBe('Mas 4 emaily!');
expect(element('ng-pluralize:first').html()).toBe('Mas 4 emaily!');
input('plInput').enter('6');
expect(element('.ng-pluralize:first').html()).toBe('Mas 6 emailov!');
expect(element('ng-pluralize:first').html()).toBe('Mas 6 emailov!');
});
it('should show pluralized strings with offsets', function() {
@@ -147,38 +147,38 @@ describe("localized filters", function() {
describe('ng:pluralize for zh locale', function() {
it('should show pluralized strings', function() {
expect(element('.ng-pluralize:first').html()).toBe('1人在浏览该文件!');
expect(element('ng-pluralize:first').html()).toBe('1人在浏览该文件!');
input('plInput').enter('0');
expect(element('.ng-pluralize:first').html()).toBe('0人在浏览该文件!');
expect(element('ng-pluralize:first').html()).toBe('0人在浏览该文件!');
input('plInput').enter('3');
expect(element('.ng-pluralize:first').html()).toBe('3人在浏览该文件!');
expect(element('ng-pluralize:first').html()).toBe('3人在浏览该文件!');
});
it('should show pluralized strings with offsets', function() {
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian 在浏览该文件!');
input('plInput2').enter('0');
expect(element('.ng-pluralize:last').html()).toBe('没有人在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('没有人在浏览该文件!');
input('plInput2').enter('2');
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
input('plInput2').enter('3');
expect(element('.ng-pluralize:last').html()).
expect(element('ng-pluralize:last').html()).
toBe('Shanjian, Di 还有其他1 人在浏览该文件!');
});
it('should show pluralized strings with correct data-binding', function() {
input('plInput2').enter('2');
expect(element('.ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('Shanjian 和 Di 在浏览该文件!');
input('person1').enter('彭迪');
expect(element('.ng-pluralize:last').html()).toBe('彭迪 和 Di 在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('彭迪 和 Di 在浏览该文件!');
input('person2').enter('一哥');
expect(element('.ng-pluralize:last').html()).toBe('彭迪 和 一哥 在浏览该文件!');
expect(element('ng-pluralize:last').html()).toBe('彭迪 和 一哥 在浏览该文件!');
});
})
});
+4 -4
View File
@@ -6,13 +6,13 @@
<script src="../../build/angular.js"></script>
<script src="../../build/i18n/angular-locale_cs.js"></script>
<script>
function AppCntl(){
this.input = 234234443432;
function AppCntl($scope){
$scope.input = 234234443432;
}
</script>
</head>
<body ng:controller="AppCntl">
<input type="text" ng:model="input"><br>
<body ng-controller="AppCntl">
<input type="text" ng-model="input"><br>
date: {{input | date:"medium"}}<br>
date: {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
+4 -4
View File
@@ -6,13 +6,13 @@
<script src="../../build/angular.js"></script>
<script src="../../build/i18n/angular-locale_de.js"></script>
<script>
function AppCntl(){
this.input = 234234443432;
function AppCntl($scope){
$scope.input = 234234443432;
}
</script>
</head>
<body ng:controller="AppCntl">
<input type="text" ng:model="input"><br>
<body ng-controller="AppCntl">
<input type="text" ng-model="input"><br>
date: {{input | date:"medium"}}<br>
date: {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
+16 -16
View File
@@ -8,41 +8,41 @@
<script src="../../build/i18n/angular-locale_en.js"></script>
-->
<script>
function AppCntl(){
this.input = 234234443432;
this.plInput = 1;
this.person1 = "Shanjian";
this.person2 = "Di";
this.plInput2 = 1;
function AppCntl($scope){
$scope.input = 234234443432;
$scope.plInput = 1;
$scope.person1 = "Shanjian";
$scope.person2 = "Di";
$scope.plInput2 = 1;
}
</script>
</head>
<body ng:controller="AppCntl">
<body ng-controller="AppCntl">
<h3>Datetime/Number/Currency filters demo:</h3>
<input type="text" ng:model="input" value="234234443432"><br>
<input type="text" ng-model="input" value="234234443432"><br>
date(medium): {{input | date:"medium"}}<br>
date(longDate): {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
currency: {{input | currency }}
<hr/>
<h3>Pluralization demo:</h3>
<input type="text" ng:model="plInput"><br>
<ng:pluralize count="plInput"
<input type="text" ng-model="plInput"><br>
<ng-pluralize count="plInput"
when= "{ '0': 'You have no email!',
'one': 'You have one email!',
'other': 'You have {} emails!'}">
</ng:pluralize>
</ng-pluralize>
<hr/>
<h3>Pluralization demo with offsets:</h3>
Name of person1:<input type="text" ng:model="person1"/><br/>
Name of person2:<input type="text" ng:model="person2"/><br/>
<input type="text" ng:model="plInput2"><br>
<ng:pluralize count="plInput2" offset=2
Name of person1:<input type="text" ng-model="person1"/><br/>
Name of person2:<input type="text" ng-model="person2"/><br/>
<input type="text" ng-model="plInput2"><br>
<ng-pluralize count="plInput2" offset=2
when= "{'0':'Nobody is viewing!',
'1': '{{person1}} is viewing!',
'2': '{{person1}} and {{person2}} are viewing!',
'3': '{{person1}}, {{person2}} and one other person are viewing!',
'other': '{{person1}}, {{person2}} and {} other people are viewing!'}">
</ng:pluralize>
</ng-pluralize>
</body>
</html>
+4 -4
View File
@@ -6,13 +6,13 @@
<script src="../../build/angular.js"></script>
<script src="../../build/i18n/angular-locale_es.js"></script>
<script>
function AppCntl(){
this.input = 234234443432;
function AppCntl($scope){
$scope.input = 234234443432;
}
</script>
</head>
<body ng:controller="AppCntl">
<input type="text" ng:model="input" value="234234443432"><br>
<body ng-controller="AppCntl">
<input type="text" ng-model="input" value="234234443432"><br>
date: {{input | date:"medium"}}<br>
date: {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
+8 -8
View File
@@ -6,24 +6,24 @@
<script src="../../build/angular.js"></script>
<script src="../../build/i18n/angular-locale_sk-sk.js"></script>
<script>
function AppCntl(){
this.input = 234234443432;
this.plInput = 1;
function AppCntl($scope){
$scope.input = 234234443432;
$scope.plInput = 1;
}
</script>
</head>
<body ng:controller="AppCntl">
<input type="text" ng:model="input" value="234234443432"><br>
<body ng-controller="AppCntl">
<input type="text" ng-model="input" value="234234443432"><br>
date: {{input | date:"medium"}}<br>
date: {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
currency: {{input | currency }}
<hr/>
<input type="text" ng:model="plInput"><br>
<ng:pluralize count="plInput"
<input type="text" ng-model="plInput"><br>
<ng-pluralize count="plInput"
when= "{ 'one': 'Mas jeden email!',
'few': 'Mas {} emaily!',
'other': 'Mas {} emailov!'}">
</ng:pluralize>
</ng-pluralize>
</body>
</html>
+16 -16
View File
@@ -6,38 +6,38 @@
<script src="../../build/angular.js"></script>
<script src="../../build/i18n/angular-locale_zh-cn.js"></script>
<script>
function AppCntl(){
this.input = 234234443432;
this.plInput = 1;
this.person1 = "Shanjian";
this.person2 = "Di";
this.plInput2 = 1;
function AppCntl($scope){
$scope.input = 234234443432;
$scope.plInput = 1;
$scope.person1 = "Shanjian";
$scope.person2 = "Di";
$scope.plInput2 = 1;
}
</script>
</head>
<body ng:controller="AppCntl">
<body ng-controller="AppCntl">
<h3>Datetime/Number/Currency filters demo:</h3>
<input type="text" ng:model="input"><br>
<input type="text" ng-model="input"><br>
date(medium): {{input | date:"medium"}}<br>
date(longDate): {{input | date:"longDate"}}<br>
number: {{input | number}}<br>
currency: {{input | currency }}
<hr/>
<h3>Pluralization demo:</h3>
<input type="text" ng:model="plInput"><br>
<ng:pluralize count="plInput"
<input type="text" ng-model="plInput"><br>
<ng-pluralize count="plInput"
when= "{'other':'{}人在浏览该文件!'}">
</ng:pluralize>
</ng-pluralize>
<hr/>
<h3>Pluralization demo with offsets:</h3>
Name of person1:<input type="text" ng:model="person1"/><br/>
Name of person2:<input type="text" ng:model="person2"/><br/>
<input type="text" ng:model="plInput2"><br>
<ng:pluralize count="plInput2" offset=2
Name of person1:<input type="text" ng-model="person1"/><br/>
Name of person2:<input type="text" ng-model="person2"/><br/>
<input type="text" ng-model="plInput2"><br>
<ng-pluralize count="plInput2" offset=2
when= "{'0':'没有人在浏览该文件!',
'1': '{{person1}} 在浏览该文件!',
'2': '{{person1}} 和 {{person2}} 在浏览该文件!',
'other': '{{person1}}, {{person2}} 还有其他{} 人在浏览该文件!'}">
</ng:pluralize>
</ng-pluralize>
</body>
</html>
+1 -1
View File
@@ -12,7 +12,7 @@ var Q = require('qq'),
require: function() {},
i18n: {currency: {}, pluralRules: {}} };
createFolder('../locale/').then(function() {
createFolder('../../src/ngLocale/').then(function() {
var promiseA = Q.defer(),
promiseB = Q.defer();
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
function catch_errors() {
echo "ERROR. That's life."
exit 1
}
trap catch_errors ERR
TMP_FILE='changelog.tmp'
CHANGELOG_FILE='CHANGELOG.md'
echo "Getting current version..."
VERSION=`./version.js --current`
echo "Generating changelog..."
./changelog.js $VERSION $TMP_FILE
cat $CHANGELOG_FILE >> $TMP_FILE
mv -f $TMP_FILE $CHANGELOG_FILE
echo "Updating version..."
./version.js --remove-snapshot
echo "CONFIRM TO COMMIT"
read WHATEVER
echo "Creating commit..."
git commit version.yaml CHANGELOG.md -m "chore(relase): cutting the v$VERSION release"
echo "Creating tag..."
git tag "v$VERSION"
+77 -13
View File
@@ -61,7 +61,7 @@ var $boolean = 'boolean',
$undefined = 'undefined',
Error = window.Error,
/** holds major version number for IE or NaN for real browsers */
msie = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10),
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]),
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
@@ -73,8 +73,7 @@ var $boolean = 'boolean',
angularModule,
/** @name angular.module.ng */
nodeName_,
uid = ['0', '0', '0'],
DATE_ISOSTRING_LN = 24;
uid = ['0', '0', '0'];
/**
* @ngdoc function
@@ -118,8 +117,11 @@ function forEach(obj, iterator, context) {
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
} else {
for (key in obj)
iterator.call(context, obj[key], key);
for (key in obj) {
if (obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key);
}
}
}
}
return obj;
@@ -206,6 +208,10 @@ function extend(dst) {
return dst;
}
function int(str) {
return parseInt(str, 10);
}
function inherit(parent, extra) {
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
@@ -592,16 +598,16 @@ function copy(source, destination){
/**
* Create a shallow copy of an object
* @param src
*/
function shallowCopy(src) {
var dst = {},
key;
for(key in src) {
if (src.hasOwnProperty(key)) {
function shallowCopy(src, dst) {
dst = dst || {};
for(var key in src) {
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
dst[key] = src[key];
}
}
return dst;
}
@@ -721,6 +727,59 @@ function bind(self, fn) {
}
}
function toJsonReplacer(key, value) {
var val = value;
if (/^\$+/.test(key)) {
val = undefined;
} else if (isWindow(value)) {
val = '$WINDOW';
} else if (value && document === value) {
val = '$DOCUMENT';
} else if (isScope(value)) {
val = '$SCOPE';
}
return val;
};
/**
* @ngdoc function
* @name angular.toJson
* @function
*
* @description
* Serializes input into a JSON-formatted string.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
* @returns {string} Jsonified string representing `obj`.
*/
function toJson(obj, pretty) {
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
}
/**
* @ngdoc function
* @name angular.fromJson
* @function
*
* @description
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
* @returns {Object|Array|Date|string|number} Deserialized thingy.
*/
function fromJson(json) {
return isString(json)
? JSON.parse(json)
: json;
}
function toBoolean(value) {
if (value && value.length !== 0) {
var v = lowercase("" + value);
@@ -928,6 +987,7 @@ function bindJQuery() {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
@@ -935,7 +995,7 @@ function bindJQuery() {
JQLitePatchJQueryRemove('empty');
JQLitePatchJQueryRemove('html');
} else {
jqLite = jqLiteWrap;
jqLite = JQLite;
}
angular.element = jqLite;
}
@@ -952,7 +1012,11 @@ function assertArg(arg, name, reason) {
return arg;
}
function assertArgFn(arg, name) {
function assertArgFn(arg, name, acceptArrayAnnotation) {
if (acceptArrayAnnotation && isArray(arg)) {
arg = arg[arg.length - 1];
}
assertArg(isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg;
+2 -5
View File
@@ -74,7 +74,6 @@ function publishExternalAPI(angular){
ngBindHtml: ngBindHtmlDirective,
ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective,
ngBindTemplate: ngBindTemplateDirective,
ngBindAttr: ngBindAttrDirective,
ngClass: ngClassDirective,
ngClassEven: ngClassEvenDirective,
ngClassOdd: ngClassOddDirective,
@@ -101,7 +100,8 @@ function publishExternalAPI(angular){
ngChange: ngChangeDirective,
ngModelInstant: ngModelInstantDirective,
required: requiredDirective,
ngRequired: requiredDirective
ngRequired: requiredDirective,
ngValue: ngValueDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
@@ -110,8 +110,6 @@ function publishExternalAPI(angular){
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
$cookies: $CookiesProvider,
$cookieStore: $CookieStoreProvider,
$defer: $DeferProvider,
$document: $DocumentProvider,
$exceptionHandler: $ExceptionHandlerProvider,
@@ -122,7 +120,6 @@ function publishExternalAPI(angular){
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
$resource: $ResourceProvider,
$route: $RouteProvider,
$routeParams: $RouteParamsProvider,
$rootScope: $RootScopeProvider,
-200
View File
@@ -1,200 +0,0 @@
'use strict';
var array = [].constructor;
/**
* @ngdoc function
* @name angular.toJson
* @function
*
* @description
* Serializes input into a JSON-formatted string.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
* @returns {string} Jsonified string representing `obj`.
*/
function toJson(obj, pretty) {
var buf = [];
toJsonArray(buf, obj, pretty ? "\n " : null, []);
return buf.join('');
}
/**
* @ngdoc function
* @name angular.fromJson
* @function
*
* @description
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
* @param {boolean} [useNative=false] Use native JSON parser, if available.
* @returns {Object|Array|Date|string|number} Deserialized thingy.
*/
function fromJson(json, useNative) {
if (!isString(json)) return json;
var obj;
if (useNative && window.JSON && window.JSON.parse) {
obj = JSON.parse(json);
} else {
obj = parseJson(json, true)();
}
return transformDates(obj);
// TODO make forEach optionally recursive and remove this function
// TODO(misko): remove this once the $http service is checked in.
function transformDates(obj) {
if (isString(obj) && obj.length === DATE_ISOSTRING_LN) {
return jsonStringToDate(obj);
} else if (isArray(obj) || isObject(obj)) {
forEach(obj, function(val, name) {
obj[name] = transformDates(val);
});
}
return obj;
}
}
var R_ISO8061_STR = /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3}))?)?)?Z)?$/;
function jsonStringToDate(string){
var match;
if (isString(string) && (match = string.match(R_ISO8061_STR))){
var date = new Date(0);
date.setUTCFullYear(match[1], match[2] - 1, match[3]);
date.setUTCHours(match[4]||0, match[5]||0, match[6]||0, match[7]||0);
return date;
}
return string;
}
function jsonDateToString(date){
if (!date) return date;
var isoString = date.toISOString ? date.toISOString() : '';
return (isoString.length==24)
? isoString
: padNumber(date.getUTCFullYear(), 4) + '-' +
padNumber(date.getUTCMonth() + 1, 2) + '-' +
padNumber(date.getUTCDate(), 2) + 'T' +
padNumber(date.getUTCHours(), 2) + ':' +
padNumber(date.getUTCMinutes(), 2) + ':' +
padNumber(date.getUTCSeconds(), 2) + '.' +
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
}
function quoteUnicode(string) {
var chars = ['"'];
for ( var i = 0; i < string.length; i++) {
var code = string.charCodeAt(i);
var ch = string.charAt(i);
switch(ch) {
case '"': chars.push('\\"'); break;
case '\\': chars.push('\\\\'); break;
case '\n': chars.push('\\n'); break;
case '\f': chars.push('\\f'); break;
case '\r': chars.push(ch = '\\r'); break;
case '\t': chars.push(ch = '\\t'); break;
default:
if (32 <= code && code <= 126) {
chars.push(ch);
} else {
var encode = "000" + code.toString(16);
chars.push("\\u" + encode.substring(encode.length - 4));
}
}
}
chars.push('"');
return chars.join('');
}
function toJsonArray(buf, obj, pretty, stack) {
if (isObject(obj)) {
if (obj === window) {
buf.push('WINDOW');
return;
}
if (obj === document) {
buf.push('DOCUMENT');
return;
}
if (includes(stack, obj)) {
buf.push('RECURSION');
return;
}
stack.push(obj);
}
if (obj === null) {
buf.push('null');
} else if (obj instanceof RegExp) {
buf.push(quoteUnicode(obj.toString()));
} else if (isFunction(obj)) {
return;
} else if (isBoolean(obj)) {
buf.push('' + obj);
} else if (isNumber(obj)) {
if (isNaN(obj)) {
buf.push('null');
} else {
buf.push('' + obj);
}
} else if (isString(obj)) {
return buf.push(quoteUnicode(obj));
} else if (isObject(obj)) {
if (isArray(obj)) {
buf.push("[");
var len = obj.length;
var sep = false;
for(var i=0; i<len; i++) {
var item = obj[i];
if (sep) buf.push(",");
if (!(item instanceof RegExp) && (isFunction(item) || isUndefined(item))) {
buf.push('null');
} else {
toJsonArray(buf, item, pretty, stack);
}
sep = true;
}
buf.push("]");
} else if (isElement(obj)) {
// TODO(misko): maybe in dev mode have a better error reporting?
buf.push('DOM_ELEMENT');
} else if (isDate(obj)) {
buf.push(quoteUnicode(jsonDateToString(obj)));
} else {
buf.push("{");
if (pretty) buf.push(pretty);
var comma = false;
var childPretty = pretty ? pretty + " " : false;
var keys = [];
for(var k in obj) {
if (k!='this' && k!='$parent' && k.substring(0,2) != '$$' && obj.hasOwnProperty(k) && obj[k] !== undefined) {
keys.push(k);
}
}
keys.sort();
for ( var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
var key = keys[keyIndex];
var value = obj[key];
if (!isFunction(value)) {
if (comma) {
buf.push(",");
if (pretty) buf.push(pretty);
}
buf.push(quoteUnicode(key));
buf.push(":");
toJsonArray(buf, value, childPretty, stack);
comma = true;
}
}
buf.push("}");
}
}
if (isObject(obj)) {
stack.pop();
}
}
-164
View File
@@ -1,164 +0,0 @@
'use strict';
function Route(template, defaults) {
this.template = template = template + '#';
this.defaults = defaults || {};
var urlParams = this.urlParams = {};
forEach(template.split(/\W/), function(param){
if (param && template.match(new RegExp(":" + param + "\\W"))) {
urlParams[param] = true;
}
});
}
Route.prototype = {
url: function(params) {
var self = this,
url = this.template,
encodedVal;
params = params || {};
forEach(this.urlParams, function(_, urlParam){
encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
});
url = url.replace(/\/?#$/, '');
var query = [];
forEachSorted(params, function(value, key){
if (!self.urlParams[key]) {
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
}
});
url = url.replace(/\/*$/, '');
return url + (query.length ? '?' + query.join('&') : '');
}
};
function ResourceFactory($http) {
this.$http = $http;
}
ResourceFactory.DEFAULT_ACTIONS = {
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
ResourceFactory.prototype = {
route: function(url, paramDefaults, actions){
var self = this;
var route = new Route(url);
actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions);
function extractParams(data){
var ids = {};
forEach(paramDefaults || {}, function(value, key){
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
});
return ids;
}
function Resource(value){
copy(value || {}, this);
}
forEach(actions, function(action, name){
var isPostOrPut = action.method == 'POST' || action.method == 'PUT';
Resource[name] = function(a1, a2, a3, a4) {
var params = {};
var data;
var success = noop;
var error = null;
switch(arguments.length) {
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
} else {
params = a1;
data = a2;
success = a3;
break;
}
case 1:
if (isFunction(a1)) success = a1;
else if (isPostOrPut) data = a1;
else params = a1;
break;
case 0: break;
default:
throw "Expected between 0-4 arguments [params, data, success, error], got " +
arguments.length + " arguments.";
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
self.$http({
method: action.method,
url: route.url(extend({}, extractParams(data), action.params || {}, params)),
data: data
}).then(function(response) {
var data = response.data;
if (data) {
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
}
}
(success||noop)(value, response.headers);
}, error);
return value;
};
Resource.bind = function(additionalParamDefaults){
return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
Resource.prototype['$' + name] = function(a1, a2, a3) {
var params = extractParams(this),
success = noop,
error;
switch(arguments.length) {
case 3: params = a1; success = a2; error = a3; break;
case 2:
case 1:
if (isFunction(a1)) {
success = a1;
error = a2;
} else {
params = a1;
success = a2 || noop;
}
case 0: break;
default:
throw "Expected between 1-3 arguments [params, success, error], got " +
arguments.length + " arguments.";
}
var data = isPostOrPut ? this : undefined;
Resource[name].call(this, params, data, success, error);
};
});
return Resource;
}
};
+1 -1
View File
@@ -4,7 +4,7 @@
publishExternalAPI(angular);
jqLiteWrap(document).ready(function() {
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
+1 -1
View File
@@ -38,7 +38,7 @@
* Implicit module which gets automatically added to each {@link angular.module.AUTO.$injector $injector}.
*/
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m;
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+44 -25
View File
@@ -60,9 +60,13 @@
*
* ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
*
* - `scope()` - retrieves the current Angular scope of the element.
* - `injector()` - retrieves the Angular injector associated with application that the element is
* part of.
* - `controller(name)` - retrieves the controller of the current element or its parent. By default
* retrieves controller associated with the `ng-controller` directive. If `name` is provided as
* camelCase directive name, then the controller for this directive will be retrieved (e.g.
* `'ngModel'`).
* - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link api/angular.module.ng.$rootScope.Scope scope} of the current
* element or its parent.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
@@ -164,17 +168,18 @@ function JQLitePatchJQueryRemove(name, dispatchThis) {
}
/////////////////////////////////////////////
function jqLiteWrap(element) {
if (isString(element) && element.charAt(0) != '<') {
throw new Error('selectors not implemented');
}
return new JQLite(element);
}
function JQLite(element) {
if (element instanceof JQLite) {
return element;
} else if (isString(element)) {
}
if (!(this instanceof JQLite)) {
if (isString(element) && element.charAt(0) != '<') {
throw Error('selectors not implemented');
}
return new JQLite(element);
}
if (isString(element)) {
var div = document.createElement('div');
// Read about the NoScope elements here:
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
@@ -268,6 +273,25 @@ function JQLiteAddNodes(root, elements) {
}
}
function JQLiteController(element, name) {
return JQLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
}
function JQLiteInheritedData(element, name, value) {
element = jqLite(element);
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
if(element[0].nodeType == 9) {
element = element.find('html');
}
while (element.length) {
if (value = element.data(name)) return value;
element = element.parent();
}
}
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
@@ -283,7 +307,7 @@ var JQLitePrototype = JQLite.prototype = {
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others
JQLite(window).bind('load', trigger); // fallback to window.onload for others
},
toString: function() {
var value = [];
@@ -311,7 +335,7 @@ forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), funct
BOOLEAN_ATTR[lowercase(value)] = value;
});
var BOOLEAN_ELEMENTS = {};
forEach('input,select,option,textarea,button'.split(','), function(value) {
forEach('input,select,option,textarea,button,form'.split(','), function(value) {
BOOLEAN_ELEMENTS[uppercase(value)] = true;
});
@@ -321,20 +345,16 @@ function isBooleanAttr(element, name) {
forEach({
data: JQLiteData,
inheritedData: function(element, name, value) {
element = jqLite(element);
while (element.length) {
if (value = element.data(name)) return value;
element = element.parent();
}
},
inheritedData: JQLiteInheritedData,
scope: function(element) {
return jqLite(element).inheritedData('$scope');
return JQLiteInheritedData(element, '$scope');
},
controller: JQLiteController ,
injector: function(element) {
return jqLite(element).inheritedData('$injector');
return JQLiteInheritedData(element, '$injector');
},
removeAttr: function(element,name) {
@@ -381,8 +401,7 @@ forEach({
}
} else {
return (element[name] ||
element.getAttribute(name) !== null &&
(msie < 9 ? element.getAttribute(name) !== '' : true))
(element.attributes.getNamedItem(name)|| noop).specified)
? lowercasedName
: undefined;
}
@@ -449,7 +468,7 @@ forEach({
// JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
if (((fn.length == 2 && fn !== JQLiteHasClass) ? arg1 : arg2) === undefined) {
if (((fn.length == 2 && (fn !== JQLiteHasClass && fn !== JQLiteController)) ? arg1 : arg2) === undefined) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for(i=0; i < this.length; i++) {
+12 -1
View File
@@ -167,13 +167,24 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#filter
* @methodOf angular.Module
* @param {string} name filter name
* @param {string} name Filter name.
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link angular.module.ng.$filterProvider#register $filterProvider.register()}.
*/
filter: invokeLater('$filterProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#controller
* @methodOf angular.Module
* @param {string} name Controller name.
* @param {Function} constructor Controller constructor function.
* @description
* See {@link angular.module.ng.$controllerProvider#register $controllerProvider.register()}.
*/
controller: invokeLater('$controllerProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#directive
+120 -117
View File
@@ -128,13 +128,7 @@ function $CompileProvider($provide) {
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
CONTENT_REGEXP = /\<\<content\>\>/i,
HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/,
SIDE_EFFECT_ATTRS = {};
forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) {
SIDE_EFFECT_ATTRS[name] = name;
SIDE_EFFECT_ATTRS[directiveNormalize('ng_' + name)] = name;
});
HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/;
this.directive = function registerDirective(name, directiveFactory) {
@@ -220,12 +214,88 @@ function $CompileProvider($provide) {
}
};
var Attributes = function(element, attr) {
this.$$element = element;
this.$$observers = {};
this.$attr = attr || {};
};
Attributes.prototype = {
$normalize: directiveNormalize,
/**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
* @param {string} key Normalized key. (ie ngAttribute)
* @param {string|boolean} value The value to set. If `null` attribute will be deleted.
* @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
* Defaults to true.
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
$set: function(key, value, writeAttr, attrName) {
var booleanKey = isBooleanAttr(this.$$element[0], key.toLowerCase());
if (booleanKey) {
this.$$element.prop(key, value);
attrName = booleanKey;
}
this[key] = value;
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
} else {
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
if (writeAttr !== false) {
if (value === null || value === undefined) {
this.$$element.removeAttr(attrName);
} else {
this.$$element.attr(attrName, value);
}
}
// fire observers
forEach(this.$$observers[key], function(fn) {
try {
fn(value);
} catch (e) {
$exceptionHandler(e);
}
});
},
/**
* Observe an interpolated attribute.
* The observer will never be called, if given attribute is not interpolated.
*
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(*)} fn Function that will be called whenever the attribute value changes.
*/
$observe: function(key, fn) {
// keep only observers for interpolated attrs
if (this.$$observers[key]) {
this.$$observers[key].push(fn);
}
}
};
return compile;
//================================
function compile(templateElement, transcludeFn, maxPriority) {
templateElement = jqLite(templateElement);
if (!(templateElement instanceof jqLite)) {
// jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
templateElement = jqLite(templateElement);
}
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in <span>
forEach(templateElement, function(node, index){
@@ -281,13 +351,8 @@ function $CompileProvider($provide) {
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
for(var i = 0, ii = nodeList.length; i < ii; i++) {
attrs = {
$attr: {},
$normalize: directiveNormalize,
$set: attrSetter,
$observe: interpolatedAttrObserve,
$observers: {}
};
attrs = new Attributes();
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
@@ -321,7 +386,7 @@ function $CompileProvider($provide) {
childLinkingFn = /* nodesetLinkingFn */ linkingFns[i++];
if (directiveLinkingFn) {
if (directiveLinkingFn.scope && !rootElement) {
if (directiveLinkingFn.scope) {
childScope = scope.$new(isObject(directiveLinkingFn.scope));
jqLite(node).data('$scope', childScope);
} else {
@@ -375,17 +440,19 @@ function $CompileProvider($provide) {
for (var attr, name, nName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
attr = nAttrs[j];
name = attr.name;
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
if (attr.specified) {
name = attr.name;
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
? decodeURIComponent(node.getAttribute(name, 2))
: attr.value);
if (isBooleanAttr(node, nName)) {
attrs[nName] = true; // presence means true
if (isBooleanAttr(node, nName)) {
attrs[nName] = true; // presence means true
}
addAttrInterpolateDirective(node, directives, value, nName)
addDirective(directives, nName, 'A', maxPriority);
}
addAttrInterpolateDirective(node, directives, value, nName)
addDirective(directives, nName, 'A', maxPriority);
}
// use class as directive
@@ -442,7 +509,7 @@ function $CompileProvider($provide) {
newIsolatedScopeDirective = null,
templateDirective = null,
delayedLinkingFn = null,
element = templateAttrs.$element = jqLite(templateNode),
element = templateAttrs.$$element = jqLite(templateNode),
directive,
directiveName,
template,
@@ -486,9 +553,9 @@ function $CompileProvider($provide) {
terminalPriority = directive.priority;
if (directiveValue == 'element') {
template = jqLite(templateNode);
templateNode = (element = templateAttrs.$element = jqLite(
templateNode = (element = templateAttrs.$$element = jqLite(
'<!-- ' + directiveName + ': ' + templateAttrs[directiveName] + ' -->'))[0];
template.replaceWith(templateNode);
replaceWith(rootElement, jqLite(template[0]), templateNode);
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
} else {
template = jqLite(JQLiteClone(templateNode));
@@ -610,10 +677,9 @@ function $CompileProvider($provide) {
if (templateNode === linkNode) {
attrs = templateAttrs;
} else {
attrs = shallowCopy(templateAttrs);
attrs.$element = jqLite(linkNode);
attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
}
element = attrs.$element;
element = attrs.$$element;
if (newScopeDirective && isObject(newScopeDirective.scope)) {
forEach(newScopeDirective.scope, function(mode, name) {
@@ -720,11 +786,14 @@ function $CompileProvider($provide) {
function mergeTemplateAttributes(dst, src) {
var srcAttr = src.$attr,
dstAttr = dst.$attr,
element = dst.$element;
element = dst.$$element;
// reapply the old attributes to the new element
forEach(dst, function(value, key) {
if (key.charAt(0) != '$') {
dst.$set(key, value, srcAttr[key]);
if (src[key]) {
value += (key === 'style' ? ';' : ' ') + src[key];
}
dst.$set(key, value, true, srcAttr[key]);
}
});
// copy the new attributes on the old attrs object
@@ -854,32 +923,28 @@ function $CompileProvider($provide) {
function addAttrInterpolateDirective(node, directives, value, name) {
var interpolateFn = $interpolate(value, true);
if (SIDE_EFFECT_ATTRS[name]) {
name = SIDE_EFFECT_ATTRS[name];
if (isBooleanAttr(node, name)) {
value = true;
}
} else if (!interpolateFn) {
// we are not a side-effect attr, and we have no side-effects -> ignore
return;
}
// no interpolation found -> ignore
if (!interpolateFn) return;
directives.push({
priority: 100,
compile: function(element, attr) {
if (interpolateFn) {
return function(scope, element, attr) {
// we define observers array only for interpolated attrs
// and ignore observers for non interpolated attrs to save some memory
attr.$observers[name] = [];
attr[name] = undefined;
scope.$watch(interpolateFn, function(value) {
attr.$set(name, value);
});
};
} else {
attr.$set(name, value);
compile: valueFn(function(scope, element, attr) {
if (name === 'class') {
// we need to interpolate classes again, in the case the element was replaced
// and therefore the two class attrs got merged - we want to interpolate the result
interpolateFn = $interpolate(attr[name], true);
}
}
// we define observers array only for interpolated attrs
// and ignore observers for non interpolated attrs to save some memory
attr.$$observers[name] = [];
attr[name] = undefined;
scope.$watch(interpolateFn, function(value) {
attr.$set(name, value);
});
})
});
}
@@ -911,68 +976,6 @@ function $CompileProvider($provide) {
}
element[0] = newNode;
}
/**
* Set a normalized attribute on the element in a way such that all directives
* can share the attribute. This function properly handles boolean attributes.
* @param {string} key Normalized key. (ie ngAttribute)
* @param {string|boolean} value The value to set. If `null` attribute will be deleted.
* @param {string=} attrName Optional none normalized name. Defaults to key.
*/
function attrSetter(key, value, attrName) {
var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase());
if (booleanKey) {
value = toBoolean(value);
this.$element.prop(key, value);
this[key] = value;
attrName = key = booleanKey;
value = value ? booleanKey : undefined;
} else {
this[key] = value;
}
// translate normalized key to actual key
if (attrName) {
this.$attr[key] = attrName;
} else {
attrName = this.$attr[key];
if (!attrName) {
this.$attr[key] = attrName = snake_case(key, '-');
}
}
if (value === null || value === undefined) {
this.$element.removeAttr(attrName);
} else {
this.$element.attr(attrName, value);
}
// fire observers
forEach(this.$observers[key], function(fn) {
try {
fn(value);
} catch (e) {
$exceptionHandler(e);
}
});
}
/**
* Observe an interpolated attribute.
* The observer will never be called, if given attribute is not interpolated.
*
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(*)} fn Function that will be called whenever the attribute value changes.
*/
function interpolatedAttrObserve(key, fn) {
// keep only observers for interpolated attrs
if (this.$observers[key]) {
this.$observers[key].push(fn);
}
}
}];
}
+68
View File
@@ -0,0 +1,68 @@
'use strict';
/**
* @ngdoc object
* @name angular.module.ng.$controllerProvider
* @description
* The {@link angular.module.ng.$controller $controller service} is used by Angular to create new
* controllers.
*
* This provider allows controller registration via the
* {@link angular.module.ng.$controllerProvider#register register} method.
*/
function $ControllerProvider() {
var controllers = {};
/**
* @ngdoc function
* @name angular.module.ng.$controllerProvider#register
* @methodOf angular.module.ng.$controllerProvider
* @param {string} name Controller name
* @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
* annotations in the array notation).
*/
this.register = function(name, constructor) {
controllers[name] = constructor;
};
this.$get = ['$injector', '$window', function($injector, $window) {
/**
* @ngdoc function
* @name angular.module.ng.$controller
* @requires $injector
*
* @param {Function|string} constructor If called with a function then it's considered to be the
* controller constructor function. Otherwise it's considered to be a string which is used
* to retrieve the controller constructor using the following steps:
*
* * check if a controller with given name is registered via `$controllerProvider`
* * check if evaluating the string on the current scope returns a constructor
* * check `window[constructor]` on the global `window` object
*
* @param {Object} locals Injection locals for Controller.
* @return {Object} Instance of given controller.
*
* @description
* `$controller` service is responsible for instantiating controllers.
*
* It's just simple call to {@link angular.module.AUTO.$injector $injector}, but extracted into
* a service, so that one can override this service with {@link https://gist.github.com/1649788
* BC version}.
*/
return function(constructor, locals) {
if(isString(constructor)) {
var name = constructor;
constructor = controllers.hasOwnProperty(name)
? controllers[name]
: getter(locals.$scope, name, true) || getter($window, name, true);
assertArgFn(constructor, name, true);
}
return $injector.instantiate(constructor, locals);
};
}];
}
@@ -130,7 +130,7 @@
<doc:example>
<doc:source>
Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
<button ng-model="button" ng-disabled="{{checked}}">Button</button>
<button ng-model="button" ng-disabled="checked">Button</button>
</doc:source>
<doc:scenario>
it('should toggle button', function() {
@@ -142,7 +142,7 @@
</doc:example>
*
* @element INPUT
* @param {template} ng-disabled any string which can contain '{{}}' markup.
* @param {string} expression Angular expression that will be evaluated.
*/
@@ -160,7 +160,7 @@
<doc:example>
<doc:source>
Check me to check both: <input type="checkbox" ng-model="master"><br/>
<input id="checkSlave" type="checkbox" ng-checked="{{master}}">
<input id="checkSlave" type="checkbox" ng-checked="master">
</doc:source>
<doc:scenario>
it('should check both checkBoxes', function() {
@@ -172,7 +172,7 @@
</doc:example>
*
* @element INPUT
* @param {template} ng-checked any string which can contain '{{}}' markup.
* @param {string} expression Angular expression that will be evaluated.
*/
@@ -191,7 +191,7 @@
<doc:example>
<doc:source>
Check me check multiple: <input type="checkbox" ng-model="checked"><br/>
<select id="select" ng-multiple="{{checked}}">
<select id="select" ng-multiple="checked">
<option>Misko</option>
<option>Igor</option>
<option>Vojta</option>
@@ -208,7 +208,7 @@
</doc:example>
*
* @element SELECT
* @param {template} ng-multiple any string which can contain '{{}}' markup.
* @param {string} expression Angular expression that will be evaluated.
*/
@@ -226,7 +226,7 @@
<doc:example>
<doc:source>
Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
<input type="text" ng-readonly="{{checked}}" value="I'm Angular"/>
<input type="text" ng-readonly="checked" value="I'm Angular"/>
</doc:source>
<doc:scenario>
it('should toggle readonly attr', function() {
@@ -238,7 +238,7 @@
</doc:example>
*
* @element INPUT
* @param {template} ng-readonly any string which can contain '{{}}' markup.
* @param {string} expression Angular expression that will be evaluated.
*/
@@ -255,35 +255,62 @@
* @example
<doc:example>
<doc:source>
Check me to select: <input type="checkbox" ng-model="checked"><br/>
Check me to select: <input type="checkbox" ng-model="selected"><br/>
<select>
<option>Hello!</option>
<option id="greet" ng-selected="{{checked}}">Greetings!</option>
<option id="greet" ng-selected="selected">Greetings!</option>
</select>
</doc:source>
<doc:scenario>
it('should select Greetings!', function() {
expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
input('checked').check();
input('selected').check();
expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
});
</doc:scenario>
</doc:example>
*
* @element OPTION
* @param {template} ng-selected any string which can contain '{{}}' markup.
* @param {string} expression Angular expression that will be evaluated.
*/
function ngAttributeAliasDirective(propName, attrName) {
ngAttributeAliasDirectives[directiveNormalize('ng-' + attrName)] = valueFn(
function(scope, element, attr) {
attr.$observe(directiveNormalize('ng-' + attrName), function(value) {
attr.$set(attrName, value);
});
}
);
}
var ngAttributeAliasDirectives = {};
forEach(BOOLEAN_ATTR, ngAttributeAliasDirective);
ngAttributeAliasDirective(null, 'src');
// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = function() {
return {
priority: 100,
compile: function(tpl, attr) {
return function(scope, element, attr) {
attr.$$observers[attrName] = [];
scope.$watch(attr[normalized], function(value) {
attr.$set(attrName, value);
});
};
}
};
};
});
// ng-src, ng-href are interpolated
forEach(['src', 'href'], function(attrName) {
var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = function() {
return {
priority: 100,
compile: function(tpl, attr) {
return function(scope, element, attr) {
attr.$$observers[attrName] = [];
attr.$observe(normalized, function(value) {
attr.$set(attrName, value);
});
};
}
};
};
});
@@ -31,10 +31,11 @@ var nullFormCtrl = {
* of `FormController`.
*
*/
FormController.$inject = ['name', '$element', '$attrs'];
function FormController(name, element, attrs) {
//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope'];
function FormController(element, attrs) {
var form = this,
parentForm = element.parent().inheritedData('$formController') || nullFormCtrl,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
errors = form.$error = {};
@@ -45,9 +46,6 @@ function FormController(name, element, attrs) {
form.$valid = true;
form.$invalid = false;
// publish the form into scope
name(this);
parentForm.$addControl(form);
// Setup initial state of the control
@@ -130,9 +128,24 @@ function FormController(name, element, attrs) {
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng-form
* @restrict EAC
*
* @description
* Nestable alias of {@link angular.module.ng.$compileProvider.directive.form `form`} directive. HTML
* does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
* sub-group of controls needs to be determined.
*
* @param {string=} ng-form|name Name of the form. If specified, the form controller will be published into
* related scope, under this name.
*
*/
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.form
* @restrict EA
* @restrict E
*
* @description
* Directive that instantiates
@@ -141,12 +154,12 @@ function FormController(name, element, attrs) {
* If `name` attribute is specified, the form controller is published onto the current scope under
* this name.
*
* # Alias: `ng-form`
* # Alias: {@link angular.module.ng.$compileProvider.directive.ng-form `ng-form`}
*
* In angular forms can be nested. This means that the outer form is valid when all of the child
* forms are valid as well. However browsers do not allow nesting of `<form>` elements, for this
* reason angular provides `<ng-form>` alias which behaves identical to `<form>` but allows
* element nesting.
* reason angular provides {@link angular.module.ng.$compileProvider.directive.ng-form `ng-form`} alias
* which behaves identical to `<form>` but allows form nesting.
*
*
* # CSS classes
@@ -218,25 +231,31 @@ function FormController(name, element, attrs) {
</doc:scenario>
</doc:example>
*/
var formDirectiveDev = {
var formDirectiveDir = {
name: 'form',
restrict: 'E',
inject: {
name: 'accessor'
},
controller: FormController,
compile: function() {
return {
pre: function(scope, formElement, attr, controller) {
formElement.bind('submit', function(event) {
if (!attr.action) event.preventDefault();
});
if (!attr.action) {
formElement.bind('submit', function(event) {
event.preventDefault();
});
}
var parentFormCtrl = formElement.parent().inheritedData('$formController');
var parentFormCtrl = formElement.parent().controller('form'),
alias = attr.name || attr.ngForm;
if (alias) {
scope[alias] = controller;
}
if (parentFormCtrl) {
formElement.bind('$destroy', function() {
parentFormCtrl.$removeControl(controller);
if (attr.name) delete scope[attr.name];
if (alias) {
scope[alias] = undefined;
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
}
@@ -245,5 +264,5 @@ var formDirectiveDev = {
}
};
var formDirective = valueFn(formDirectiveDev);
var ngFormDirective = valueFn(extend(copy(formDirectiveDev), {restrict:'EAC'}));
var formDirective = valueFn(formDirectiveDir);
var ngFormDirective = valueFn(extend(copy(formDirectiveDir), {restrict: 'EAC'}));
@@ -415,7 +415,7 @@ function textInputType(scope, element, attr, ctrl) {
// min length validator
if (attr.ngMinlength) {
var minlength = parseInt(attr.ngMinlength, 10);
var minlength = int(attr.ngMinlength);
var minLengthValidator = function(value) {
if (!isEmpty(value) && value.length < minlength) {
ctrl.$setValidity('minlength', false);
@@ -432,7 +432,7 @@ function textInputType(scope, element, attr, ctrl) {
// max length validator
if (attr.ngMaxlength) {
var maxlength = parseInt(attr.ngMaxlength, 10);
var maxlength = int(attr.ngMaxlength);
var maxLengthValidator = function(value) {
if (!isEmpty(value) && value.length > maxlength) {
ctrl.$setValidity('maxlength', false);
@@ -558,8 +558,10 @@ function radioInputType(scope, element, attr, ctrl) {
ctrl.$render = function() {
var value = attr.value;
element[0].checked = isDefined(value) && (value == ctrl.$viewValue);
element[0].checked = (value == ctrl.$viewValue);
};
attr.$observe('value', ctrl.$render);
}
function checkboxInputType(scope, element, attr, ctrl) {
@@ -669,7 +671,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
</doc:source>
<doc:scenario>
it('should initialize to model', function() {
expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');
expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
expect(binding('myForm.userName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
@@ -683,7 +685,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
it('should be valid if empty when min length is set', function() {
input('user.last').enter('');
expect(binding('user')).toEqual('{"last":"","name":"guest"}');
expect(binding('user')).toEqual('{"name":"guest","last":""}');
expect(binding('myForm.lastName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
@@ -1166,3 +1168,26 @@ var ngListDirective = function() {
}
};
};
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
var ngValueDirective = [function() {
return {
priority: 100,
compile: function(tpl, attr) {
if (CONSTANT_VALUE_REGEXP.test(attr.ngValue)) {
return function(scope) {
attr.$set('value', scope.$eval(attr.ngValue));
};
} else {
return function(scope, elm, attr) {
attr.$$observers.value = [];
scope.$watch(attr.ngValue, function(value) {
attr.$set('value', value, false);
});
};
}
}
};
}];
@@ -26,7 +26,7 @@
}
</script>
<div ng-controller="Ctrl">
Enter name: <input type="text" ng-model="name"> <br/>
Enter name: <input type="text" ng-model="name" ng-model-instant><br>
Hello <span ng-bind="name"></span>!
</div>
</doc:source>
@@ -122,8 +122,8 @@ var ngBindHtmlDirective = ['$sanitize', function($sanitize) {
}
</script>
<div ng-controller="Ctrl">
Salutation: <input type="text" ng-model="salutation"><br/>
Name: <input type="text" ng-model="name"><br/>
Salutation: <input type="text" ng-model="salutation" ng-model-instant><br>
Name: <input type="text" ng-model="name" ng-model-instant><br>
<pre ng-bind-template="{{salutation}} {{name}}!"></pre>
</div>
</doc:source>
@@ -153,98 +153,3 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
});
}
}];
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng-bind-attr
* @restrict A
*
* @description
* The `ng-bind-attr` attribute specifies that a
* {@link guide/dev_guide.templates.databinding databinding} should be created between a particular
* element attribute and a given expression. Unlike `ng-bind`, the `ng-bind-attr` contains one or
* more JSON key value pairs; each pair specifies an attribute and the
* {@link guide/dev_guide.expressions expression} to which it will be mapped.
*
* Instead of writing `ng-bind-attr` statements in your HTML, you can use double-curly markup to
* specify an <tt ng-non-bindable>{{expression}}</tt> for the value of an attribute.
* At compile time, the attribute is translated into an
* `<span ng-bind-attr="{attr:expression}"></span>`.
*
* The following HTML snippet shows how to specify `ng-bind-attr`:
* <pre>
* <a ng-bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a>
* </pre>
*
* This is cumbersome, so as we mentioned using double-curly markup is a prefered way of creating
* this binding:
* <pre>
* <a href="http://www.google.com/search?q={{query}}">Google</a>
* </pre>
*
* During compilation, the template with attribute markup gets translated to the ng-bind-attr form
* mentioned above.
*
* _Note_: You might want to consider using {@link angular.module.ng.$compileProvider.directive.ng-href ng-href} instead of
* `href` if the binding is present in the main application template (`index.html`) and you want to
* make sure that a user is not capable of clicking on raw/uncompiled link.
*
*
* @element ANY
* @param {string} ng-bind-attr one or more JSON key-value pairs representing
* the attributes to replace with expressions. Each key matches an attribute
* which needs to be replaced. Each value is a text template of
* the attribute with the embedded
* <tt ng-non-bindable>{{expression}}</tt>s. Any number of
* key-value pairs can be specified.
*
* @example
* Enter a search string in the Live Preview text box and then click "Google". The search executes instantly.
<doc:example>
<doc:source>
<script>
function Ctrl($scope) {
$scope.query = 'AngularJS';
}
</script>
<div ng-controller="Ctrl">
Google for:
<input type="text" ng-model="query"/>
<a ng-bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>
Google
</a> (ng-bind-attr) |
<a href="http://www.google.com/search?q={{query}}">Google</a>
(curly binding in attribute val)
</div>
</doc:source>
<doc:scenario>
it('should check ng-bind-attr', function() {
expect(using('.doc-example-live').element('a').attr('href')).
toBe('http://www.google.com/search?q=AngularJS');
using('.doc-example-live').input('query').enter('google');
expect(using('.doc-example-live').element('a').attr('href')).
toBe('http://www.google.com/search?q=google');
});
</doc:scenario>
</doc:example>
*/
var ngBindAttrDirective = ['$interpolate', function($interpolate) {
return function(scope, element, attr) {
var lastValue = {};
var interpolateFns = {};
scope.$watch(function() {
var values = scope.$eval(attr.ngBindAttr);
for(var key in values) {
var exp = values[key],
fn = (interpolateFns[exp] ||
(interpolateFns[values[key]] = $interpolate(exp))),
value = fn(scope);
if (lastValue[key] !== value) {
attr.$set(key, lastValue[key] = value);
}
}
});
}
}];
@@ -95,9 +95,9 @@
</doc:scenario>
</doc:example>
*/
var ngControllerDirective = ['$controller', '$window', function($controller, $window) {
var ngControllerDirective = [function() {
return {
scope: true,
controller: '@'
}
};
}];
@@ -73,10 +73,10 @@ var ngRepeatDirective = ngDirective({
}
lhs = match[1];
rhs = match[2];
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
if (!match) {
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
keyValue + "'.");
lhs + "'.");
}
valueIdent = match[3] || match[1];
keyIdent = match[2];
@@ -149,7 +149,8 @@ var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$c
lastScope = current.scope = scope.$new();
if (current.controller) {
$controller(current.controller, {$scope: lastScope});
element.contents().
data('$ngControllerController', $controller(current.controller, {$scope: lastScope}));
}
link(lastScope);
@@ -174,6 +174,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
}
function Multiple(scope, selectElement, ctrl) {
var lastView;
ctrl.$render = function() {
var items = new HashMap(ctrl.$viewValue);
forEach(selectElement.children(), function(option) {
@@ -181,6 +182,15 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
});
};
// we have to do it on each watch since ng-model watches reference, but
// we need to work of an array, so we need to see if anything was inserted/removed
scope.$watch(function() {
if (!equals(lastView, ctrl.$viewValue)) {
lastView = copy(ctrl.$viewValue);
ctrl.$render();
}
});
selectElement.bind('change', function() {
scope.$apply(function() {
var array = [];
@@ -41,7 +41,7 @@
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
Search: <input ng-model="searchText"/>
Search: <input ng-model="searchText" ng-model-instant>
<table id="searchTextResults">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng-repeat="friend in friends | filter:searchText">
@@ -50,9 +50,9 @@
<tr>
</table>
<hr>
Any: <input ng-model="search.$"/> <br>
Name only <input ng-model="search.name"/><br>
Phone only <input ng-model="search.phone"/><br>
Any: <input ng-model="search.$" ng-model-instant> <br>
Name only <input ng-model="search.name" ng-model-instant><br>
Phone only <input ng-model="search.phone" ng-model-instant><br>
<table id="searchObjResults">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng-repeat="friend in friends | filter:search">
@@ -23,8 +23,8 @@
}
</script>
<div ng-controller="Ctrl">
<input type="number" ng-model="amount"/> <br/>
default currency symbol ($): {{amount | currency}}<br/>
<input type="number" ng-model="amount" ng-model-instant> <br>
default currency symbol ($): {{amount | currency}}<br>
custom currency identifier (USD$): {{amount | currency:"USD$"}}
</div>
</doc:source>
@@ -74,9 +74,9 @@ function currencyFilter($locale) {
}
</script>
<div ng-controller="Ctrl">
Enter number: <input ng-model='val'><br/>
Default formatting: {{val | number}}<br/>
No fractions: {{val | number:0}}<br/>
Enter number: <input ng-model='val' ng-model-instant><br>
Default formatting: {{val | number}}<br>
No fractions: {{val | number:0}}<br>
Negative number: {{-val | number:4}}
</div>
</doc:source>
@@ -288,7 +288,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
* (e.g. `"h o''clock"`).
*
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
* number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
* @param {string=} format Formatting rules (see Description). If not specified,
* `mediumDate` is used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
@@ -297,11 +298,11 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
<doc:example>
<doc:source>
<span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
{{1288323623006 | date:'medium'}}<br/>
{{1288323623006 | date:'medium'}}<br>
<span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
<span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
</doc:source>
<doc:scenario>
it('should format date', function() {
@@ -317,6 +318,27 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
*/
dateFilter.$inject = ['$locale'];
function dateFilter($locale) {
var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string){
var match;
if (match = string.match(R_ISO8601_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
return date;
}
return string;
}
return function(date, format) {
var text = '',
parts = [],
@@ -326,7 +348,7 @@ function dateFilter($locale) {
format = $locale.DATETIME_FORMATS[format] || format;
if (isString(date)) {
if (NUMBER_STRING.test(date)) {
date = parseInt(date, 10);
date = int(date);
} else {
date = jsonStringToDate(date);
}
@@ -385,7 +407,7 @@ function dateFilter($locale) {
</doc:source>
<doc:scenario>
it('should jsonify filtered objects', function() {
expect(binding("{'name':'value'}")).toBe('{\n "name":"value"}');
expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
});
</doc:scenario>
</doc:example>
@@ -446,7 +468,7 @@ var uppercaseFilter = valueFn(uppercase);
}
</script>
<div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
Snippet: <textarea ng-model="snippet" ng-model-instant cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
@@ -456,7 +478,7 @@ var uppercaseFilter = valueFn(uppercase);
<tr id="linky-filter">
<td>linky filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br/>&lt;/div&gt;</pre>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet | linky"></div>
@@ -464,7 +486,7 @@ var uppercaseFilter = valueFn(uppercase);
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
@@ -31,24 +31,24 @@
}
</script>
<div ng-controller="Ctrl">
Limit {{numbers}} to: <input type="integer" ng-model="limit"/>
<p>Output: {{ numbers | limitTo:limit | json }}</p>
Limit {{numbers}} to: <input type="integer" ng-model="limit" ng-model-instant>
<p>Output: {{ numbers | limitTo:limit }}</p>
</div>
</doc:source>
<doc:scenario>
it('should limit the numer array to first three items', function() {
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3]');
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
});
it('should update the output when -3 is entered', function() {
input('limit').enter(-3);
expect(binding('numbers | limitTo:limit | json')).toEqual('[7,8,9]');
expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
});
it('should not exceed the maximum size of input array', function() {
input('limit').enter(100);
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3,4,5,6,7,8,9]');
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
});
</doc:scenario>
</doc:example>
@@ -56,7 +56,7 @@
function limitToFilter(){
return function(array, limit) {
if (!(array instanceof Array)) return array;
limit = parseInt(limit, 10);
limit = int(limit);
var out = [],
i, n;
+34 -11
View File
@@ -90,7 +90,7 @@ function $HttpProvider() {
var $config = this.defaults = {
// transform incoming response data
transformResponse: function(data) {
transformResponse: [function(data) {
if (isString(data)) {
// strip json vulnerability protection prefix
data = data.replace(PROTECTION_PREFIX, '');
@@ -98,12 +98,12 @@ function $HttpProvider() {
data = fromJson(data, true);
}
return data;
},
}],
// transform outgoing request data
transformRequest: function(d) {
transformRequest: [function(d) {
return isObject(d) && !isFile(d) ? toJson(d) : d;
},
}],
// default headers
headers: {
@@ -151,7 +151,7 @@ function $HttpProvider() {
* For unit testing applications that use `$http` service, see
* {@link angular.module.ngMock.$httpBackend $httpBackend mock}.
*
* For a higher level of abstraction, please check out the {@link angular.module.ng.$resource
* For a higher level of abstraction, please check out the {@link angular.module.ngResource.$resource
* $resource} service.
*
* The $http API is based on the {@link angular.module.ng.$q deferred/promise APIs} exposed by
@@ -360,6 +360,8 @@ function $HttpProvider() {
*
* - **method** `{string}` HTTP method (e.g. 'GET', 'POST', etc)
* - **url** `{string}` Absolute or relative URL of the resource that is being requested.
* - **params** `{Object.<string|Object>}` Map of strings or objects which will be turned to
* `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
* - **data** `{string|Object}` Data to be sent as the request message data.
* - **headers** `{Object}` Map of strings representing HTTP headers to send to the server.
* - **transformRequest** `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
@@ -470,6 +472,10 @@ function $HttpProvider() {
reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
promise;
// strip content-type if data is undefined
if (isUndefined(config.data)) {
delete reqHeaders['Content-Type'];
}
// send request
promise = sendReq(config, reqData, reqHeaders);
@@ -634,7 +640,8 @@ function $HttpProvider() {
var deferred = $q.defer(),
promise = deferred.promise,
cache,
cachedResp;
cachedResp,
url = buildUrl(config.url, config.params);
$http.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);
@@ -645,7 +652,7 @@ function $HttpProvider() {
}
if (cache) {
cachedResp = cache.get(config.url);
cachedResp = cache.get(url);
if (cachedResp) {
if (cachedResp.then) {
// cached request has already been sent, but there is no response yet
@@ -661,13 +668,13 @@ function $HttpProvider() {
}
} else {
// put the promise for the non-transformed response into cache as a placeholder
cache.put(config.url, promise);
cache.put(url, promise);
}
}
// if we won't have the response in cache, send the request to the backend
if (!cachedResp) {
$httpBackend(config.method, config.url, reqData, done, reqHeaders, config.timeout);
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout);
}
return promise;
@@ -682,10 +689,10 @@ function $HttpProvider() {
function done(status, response, headersString) {
if (cache) {
if (isSuccess(status)) {
cache.put(config.url, [status, response, parseHeaders(headersString)]);
cache.put(url, [status, response, parseHeaders(headersString)]);
} else {
// remove promise from the cache
cache.remove(config.url);
cache.remove(url);
}
}
@@ -715,5 +722,21 @@ function $HttpProvider() {
if (idx !== -1) $http.pendingRequests.splice(idx, 1);
}
}
function buildUrl(url, params) {
if (!params) return url;
var parts = [];
forEachSorted(params, function(value, key) {
if (value == null || value == undefined) return;
if (isObject(value)) {
value = toJson(value);
}
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
}
}];
}
@@ -18,7 +18,7 @@ var XHR = window.XMLHttpRequest || function() {
* XMLHttpRequest object or JSONP and deals with browser incompatibilities.
*
* You should never need to use this service directly, instead use the higher-level abstractions:
* {@link angular.module.ng.$http $http} or {@link angular.module.ng.$resource $resource}.
* {@link angular.module.ng.$http $http} or {@link angular.module.ngResource.$resource $resource}.
*
* During testing this implementation is swapped with {@link angular.module.ngMock.$httpBackend mock
* $httpBackend} which can be trained with responses.
@@ -30,7 +30,7 @@ function matchUrl(url, obj) {
match = {
protocol: match[1],
host: match[3],
port: parseInt(match[5], 10) || DEFAULT_PORTS[match[1]] || null,
port: int(match[5]) || DEFAULT_PORTS[match[1]] || null,
path: match[6] || '/',
search: match[8],
hash: match[10]
+13 -8
View File
@@ -82,8 +82,9 @@ function $LogProvider(){
function formatError(arg) {
if (arg instanceof Error) {
if (arg.stack) {
arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ?
'Error: ' + arg.message + '\n' + arg.stack : arg.stack;
arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
? 'Error: ' + arg.message + '\n' + arg.stack
: arg.stack;
} else if (arg.sourceURL) {
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
}
@@ -92,19 +93,23 @@ function $LogProvider(){
}
function consoleLog(type) {
var console = $window.console || {};
var logFn = console[type] || console.log || noop;
var console = $window.console || {},
logFn = console[type] || console.log || noop;
if (logFn.apply) {
return function() {
var args = [];
forEach(arguments, function(arg){
forEach(arguments, function(arg) {
args.push(formatError(arg));
});
return logFn.apply(console, args);
};
} else {
// we are IE, in which case there is nothing we can do
return logFn;
}
// we are IE which either doesn't have window.console => this is noop and we do nothing,
// or we are IE where console.log doesn't have apply so we log at least first 2 args
return function(arg1, arg2) {
logFn(arg1, arg2);
}
}
}];
-6
View File
@@ -752,9 +752,3 @@ function $ParseProvider() {
};
}];
}
// This is a special access for JSON parser which bypasses the injector
var parseJson = function(json) {
return parser(json, true);
};
+13 -9
View File
@@ -364,16 +364,20 @@ function qFactory(nextTick, exceptionHandler) {
counter = promises.length,
results = [];
forEach(promises, function(promise, index) {
promise.then(function(value) {
if (index in results) return;
results[index] = value;
if (!(--counter)) deferred.resolve(results);
}, function(reason) {
if (index in results) return;
deferred.reject(reason);
if (counter) {
forEach(promises, function(promise, index) {
ref(promise).then(function(value) {
if (index in results) return;
results[index] = value;
if (!(--counter)) deferred.resolve(results);
}, function(reason) {
if (index in results) return;
deferred.reject(reason);
});
});
});
} else {
deferred.resolve(results);
}
return deferred.promise;
}
+23 -7
View File
@@ -437,6 +437,17 @@ function $RootScopeProvider(){
this.$root.$$phase = null;
},
/**
* @ngdoc event
* @name angular.module.$rootScope.Scope#$destroy
* @eventOf angular.module.ng.$rootScope.Scope
* @eventType broadcast on scope being destroyed
*
* @description
* Broadcasted when a scope and its children are being destroyed.
*/
/**
* @ngdoc function
* @name angular.module.ng.$rootScope.Scope#$destroy
@@ -445,13 +456,17 @@ function $RootScopeProvider(){
*
* @description
* Remove the current scope (and all of its children) from the parent scope. Removal implies
* that calls to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} will no longer propagate to the current
* scope and its children. Removal also implies that the current scope is eligible for garbage
* collection.
* that calls to {@link angular.module.ng.$rootScope.Scope#$digest $digest()} will no longer
* propagate to the current scope and its children. Removal also implies that the current
* scope is eligible for garbage collection.
*
* The `$destroy()` is usually used by directives such as
* {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} for managing the unrolling of the loop.
* {@link angular.module.ng.$compileProvider.directive.ng-repeat ng-repeat} for managing the
* unrolling of the loop.
*
* Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
* Application code can register a `$destroy` event handler that will give it chance to
* perform any necessary cleanup.
*/
$destroy: function() {
if (this.$root == this) return; // we can't remove the root node;
@@ -488,12 +503,13 @@ function $RootScopeProvider(){
* @param {(string|function())=} expression An angular expression to be executed.
*
* - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
* - `function(scope)`: execute the function with the current `scope` parameter.
* - `function(scope, locals)`: execute the function with the current `scope` parameter.
* @param {Object=} locals Hash object of local variables for the expression.
*
* @returns {*} The result of evaluating the expression.
*/
$eval: function(expr) {
return $parse(expr)(this);
$eval: function(expr, locals) {
return $parse(expr)(this, locals);
},
/**
+14 -1
View File
@@ -18,7 +18,10 @@ function $RouteProvider(){
* @name angular.module.ng.$routeProvider#when
* @methodOf angular.module.ng.$routeProvider
*
* @param {string} path Route path (matched against `$location.hash`)
* @param {string} path Route path (matched against `$location.path`). If `$location.path`
* contains redudant trailing slash or is missing one, the route will still match and the
* `$location.path` will be updated to add or drop the trailing slash to exacly match the
* route definition.
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
*
@@ -57,6 +60,16 @@ function $RouteProvider(){
var routeDef = routes[path];
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions?
// create redirection for trailing slashes
if (path) {
var redirectPath = (path[path.length-1] == '/')
? path.substr(0, path.length-1)
: path +'/';
routes[redirectPath] = {redirectTo: path};
}
return routeDef;
};
+2 -3
View File
@@ -1,7 +1,8 @@
'use strict';
/**
* @ngdoc object
* !!! This is an undocumented "private" service !!!
*
* @name angular.module.ng.$sniffer
* @requires $window
*
@@ -13,8 +14,6 @@
*/
function $SnifferProvider(){
this.$get = ['$window', function($window){
if ($window.Modernizr) return $window.Modernizr;
return {
history: !!($window.history && $window.history.pushState),
hashchange: 'onhashchange' in $window &&
+163
View File
@@ -0,0 +1,163 @@
'use strict';
/**
* @ngdoc overview
* @name angular.module.ngCookies
*/
angular.module('ngCookies', ['ng']).
/**
* @ngdoc object
* @name angular.module.ng.$cookies
* @requires $browser
*
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from
* this object, new cookies are created/deleted at the end of current $eval.
*
* @example
*/
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
var cookies = {},
lastCookies = {},
lastBrowserCookies,
runEval = false,
copy = angular.copy,
isUndefined = angular.isUndefined;
//creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() {
var currentCookies = $browser.cookies();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies);
copy(currentCookies, cookies);
if (runEval) $rootScope.$apply();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
}
}
//update all cookies updated in $cookies
for(name in cookies) {
value = cookies[name];
if (!angular.isString(value)) {
if (angular.isDefined(lastCookies[name])) {
cookies[name] = lastCookies[name];
} else {
delete cookies[name];
}
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//verify what was actually stored
if (updated){
updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
}
}
}]).
/**
* @ngdoc object
* @name angular.module.ng.$cookieStore
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
* @example
*/
factory('$cookieStore', ['$cookies', function($cookies) {
return {
/**
* @ngdoc method
* @name angular.module.ng.$cookieStore#get
* @methodOf angular.module.ng.$cookieStore
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
get: function(key) {
return angular.fromJson($cookies[key]);
},
/**
* @ngdoc method
* @name angular.module.ng.$cookieStore#put
* @methodOf angular.module.ng.$cookieStore
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies[key] = angular.toJson(value);
},
/**
* @ngdoc method
* @name angular.module.ng.$cookieStore#remove
* @methodOf angular.module.ng.$cookieStore
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $cookies[key];
}
};
}]);

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