Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
coopwire-hypothesis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
孙灵跃 Leon Sun
coopwire-hypothesis
Commits
b171c1bc
Commit
b171c1bc
authored
Feb 26, 2014
by
Randall Leeds
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Upgrade Angular to v1.2.13
parent
a4907fe4
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
6603 additions
and
3576 deletions
+6603
-3576
angular-resource.js
h/lib/angular-resource.js
+155
-108
angular-route.js
h/lib/angular-route.js
+188
-138
angular-sanitize.js
h/lib/angular-sanitize.js
+223
-147
angular.js
h/lib/angular.js
+6037
-3183
No files found.
h/lib/angular-resource.js
View file @
b171c1bc
/**
* @license AngularJS v1.2.
0-rc.2
* (c) 2010-201
2
Google, Inc. http://angularjs.org
* @license AngularJS v1.2.
13
* (c) 2010-201
4
Google, Inc. http://angularjs.org
* License: MIT
*/
(
function
(
window
,
angular
,
undefined
)
{
'use strict'
;
var
$resourceMinErr
=
angular
.
$$minErr
(
'$resource'
);
// Helper functions and regex to lookup a dotted path on an object
// stopping at undefined/null. The path must be composed of ASCII
// identifiers (just like $parse)
var
MEMBER_NAME_REGEX
=
/^
(\.[
a-zA-Z_$
][
0-9a-zA-Z_$
]
*
)
+$/
;
function
isValidDottedPath
(
path
)
{
return
(
path
!=
null
&&
path
!==
''
&&
path
!==
'hasOwnProperty'
&&
MEMBER_NAME_REGEX
.
test
(
'.'
+
path
));
}
function
lookupDottedPath
(
obj
,
path
)
{
if
(
!
isValidDottedPath
(
path
))
{
throw
$resourceMinErr
(
'badmember'
,
'Dotted member path "@{0}" is invalid.'
,
path
);
}
var
keys
=
path
.
split
(
'.'
);
for
(
var
i
=
0
,
ii
=
keys
.
length
;
i
<
ii
&&
obj
!==
undefined
;
i
++
)
{
var
key
=
keys
[
i
];
obj
=
(
obj
!==
null
)
?
obj
[
key
]
:
undefined
;
}
return
obj
;
}
/**
* Create a shallow copy of an object and clear other fields from the destination
*/
function
shallowClearAndCopy
(
src
,
dst
)
{
dst
=
dst
||
{};
angular
.
forEach
(
dst
,
function
(
value
,
key
){
delete
dst
[
key
];
});
for
(
var
key
in
src
)
{
if
(
src
.
hasOwnProperty
(
key
)
&&
!
(
key
.
charAt
(
0
)
===
'$'
&&
key
.
charAt
(
1
)
===
'$'
))
{
dst
[
key
]
=
src
[
key
];
}
}
return
dst
;
}
/**
* @ngdoc overview
* @name ngResource
...
...
@@ -14,12 +55,13 @@ var $resourceMinErr = angular.$$minErr('$resource');
*
* # ngResource
*
* `ngResource` is the name of the optional Angular module that adds support for interacting with
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
* `ngReource` provides the {@link ngResource.$resource `$resource`} serivce.
* The `ngResource` module provides interaction support with RESTful services
* via the $resource service.
*
* {@installModule resource}
*
* <div doc-module-components="ngResource"></div>
*
* See {@link ngResource.$resource `$resource`} for usage.
*/
...
...
@@ -63,7 +105,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
*
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
* default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#
P
arameters $http.config}:
* ng.$http#
usage_p
arameters $http.config}:
*
* {action1: {method:?, params:?, isArray:?, headers:?, ...},
* action2: {method:?, params:?, isArray:?, headers:?, ...},
...
...
@@ -71,21 +113,23 @@ var $resourceMinErr = angular.$$minErr('$resource');
*
* Where:
*
* - **`action`** – {string} – The name of action. This name becomes the name of the method on your
* resource object.
* - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
* and `JSONP`.
* - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the
* parameter value is a function, it will be executed every time when a param value needs to be
* obtained for a request (unless the param was overridden).
* - **`url`** – {string} – action specific `url` override. The url templating is supported just like
* for the resource-level urls.
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see
* `returns` section.
* - **`transformRequest`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* - **`action`** – {string} – The name of action. This name becomes the name of the method on
* your resource object.
* - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`,
* `DELETE`, and `JSONP`.
* - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
* the parameter value is a function, it will be executed every time when a param value needs to
* be obtained for a request (unless the param was overridden).
* - **`url`** – {string} – action specific `url` override. The url templating is supported just
* like for the resource-level urls.
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
* see `returns` section.
* - **`transformRequest`** –
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
* - **`transformResponse`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* - **`transformResponse`** –
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
...
...
@@ -94,7 +138,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
* caching.
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
* should abort the request when resolved.
* - **`withCredentials`** - `{boolean}` - whether to
to
set the `withCredentials` flag on the
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
* requests with credentials} for more information.
* - **`responseType`** - `{string}` - see {@link
...
...
@@ -131,7 +175,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
* object results in no rendering, once the data arrives from the server then the object is
* populated with the data and the view automatically re-renders itself showing the new data. This
* means that in most case one never has to write a callback function for the action methods.
* means that in most case
s
one never has to write a callback function for the action methods.
*
* The action methods on the class object or instance object can be invoked with the following
* parameters:
...
...
@@ -153,14 +197,15 @@ var $resourceMinErr = angular.$$minErr('$resource');
*
* On success, the promise is resolved with the same resource instance or collection object,
* updated with data from server. This makes it easy to use in
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
rendering
* until the resource(s) are loaded.
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
*
rendering
until the resource(s) are loaded.
*
* On failure, the promise is resolved with the {@link ng.$http http response} object,
*
without
the `resource` property.
* On failure, the promise is resolved with the {@link ng.$http http response} object,
without
* the `resource` property.
*
* - `$resolved`: `true` after first server interaction is completed (either with success or rejection),
* `false` before that. Knowing if the Resource has been resolved is useful in data-binding.
* - `$resolved`: `true` after first server interaction is completed (either with success or
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
* data-binding.
*
* @example
*
...
...
@@ -197,14 +242,15 @@ var $resourceMinErr = angular.$$minErr('$resource');
newCard.name = "Mike Smith";
newCard.$save();
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
// server returns: {id:789, number:'0123
4
', name: 'Mike Smith'};
// server returns: {id:789, number:'0123', name: 'Mike Smith'};
expect(newCard.id).toEqual(789);
* </pre>
*
* The object returned from this function execution is a resource "class" which has "static" method
* for each action in the definition.
*
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
* `headers`.
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
...
...
@@ -217,7 +263,7 @@ var $resourceMinErr = angular.$$minErr('$resource');
});
</pre>
*
* It's worth noting that the success callback for `get`, `query` and other method gets passed
* It's worth noting that the success callback for `get`, `query` and other method
s
gets passed
* in the response that came from the server as well as $http header getter function, so one
* could rewrite the above example and get access to http headers as:
*
...
...
@@ -232,56 +278,38 @@ var $resourceMinErr = angular.$$minErr('$resource');
});
</pre>
* # Buzz client
Let's look at what a buzz client created with the `$resource` service looks like:
<doc:example>
<doc:source jsfiddle="false">
<script>
function BuzzController($resource) {
this.userId = 'googlebuzz';
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
{get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
);
}
BuzzController.prototype = {
fetch: function() {
this.activities = this.Activity.get({userId:this.userId});
},
expandReplies: function(activity) {
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
}
};
BuzzController.$inject = ['$resource'];
</script>
<div ng-controller="BuzzController">
<input ng-model="userId"/>
<button ng-click="fetch()">fetch</button>
<hr/>
<div ng-repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href ng-click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
</h1>
{{item.object.content | html}}
<div ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
</div>
</div>
</div>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
* # Creating a custom 'PUT' request
* In this example we create a custom method on our resource to make a PUT request
* <pre>
* var app = angular.module('app', ['ngResource', 'ngRoute']);
*
* // Some APIs expect a PUT request in the format URL/object/ID
* // Here we are creating an 'update' method
* app.factory('Notes', ['$resource', function($resource) {
* return $resource('/notes/:id', null,
* {
* 'update': { method:'PUT' }
* });
* }]);
*
* // In our controller we get the ID from the URL using ngRoute and $routeParams
* // We pass in $routeParams and our Notes factory along with $scope
* app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
function($scope, $routeParams, Notes) {
* // First get a note object from the factory
* var note = Notes.get({ id:$routeParams.id });
* $id = note.id;
*
* // Now call update passing in the ID first then the object you are updating
* Notes.update({ id:$id }, note);
*
* // This will PUT /notes/ID with the note object in the request payload
* }]);
* </pre>
*/
angular
.
module
(
'ngResource'
,
[
'ng'
]).
factory
(
'$resource'
,
[
'$http'
,
'$parse'
,
'$q'
,
function
(
$http
,
$parse
,
$q
)
{
factory
(
'$resource'
,
[
'$http'
,
'$q'
,
function
(
$http
,
$q
)
{
var
DEFAULT_ACTIONS
=
{
'get'
:
{
method
:
'GET'
},
'save'
:
{
method
:
'POST'
},
...
...
@@ -293,10 +321,7 @@ angular.module('ngResource', ['ng']).
forEach
=
angular
.
forEach
,
extend
=
angular
.
extend
,
copy
=
angular
.
copy
,
isFunction
=
angular
.
isFunction
,
getter
=
function
(
obj
,
path
)
{
return
$parse
(
path
)(
obj
);
};
isFunction
=
angular
.
isFunction
;
/**
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
...
...
@@ -318,9 +343,9 @@ angular.module('ngResource', ['ng']).
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a
custom
*
method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
* encoded per http://tools.ietf.org/html/rfc3986:
* This method is intended for encoding *key* or *value* parts of query component. We need a
*
custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
*
have to be
encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
...
...
@@ -352,7 +377,11 @@ angular.module('ngResource', ['ng']).
var
urlParams
=
self
.
urlParams
=
{};
forEach
(
url
.
split
(
/
\W
/
),
function
(
param
){
if
(
!
(
new
RegExp
(
"^
\\
d+$"
).
test
(
param
))
&&
param
&&
(
new
RegExp
(
"(^|[^
\\\\
]):"
+
param
+
"(
\\
W|$)"
).
test
(
url
)))
{
if
(
param
===
'hasOwnProperty'
)
{
throw
$resourceMinErr
(
'badname'
,
"hasOwnProperty is not a valid parameter name."
);
}
if
(
!
(
new
RegExp
(
"^
\\
d+$"
).
test
(
param
))
&&
param
&&
(
new
RegExp
(
"(^|[^
\\\\
]):"
+
param
+
"(
\\
W|$)"
).
test
(
url
)))
{
urlParams
[
param
]
=
true
;
}
});
...
...
@@ -363,7 +392,9 @@ angular.module('ngResource', ['ng']).
val
=
params
.
hasOwnProperty
(
urlParam
)
?
params
[
urlParam
]
:
self
.
defaults
[
urlParam
];
if
(
angular
.
isDefined
(
val
)
&&
val
!==
null
)
{
encodedVal
=
encodeUriSegment
(
val
);
url
=
url
.
replace
(
new
RegExp
(
":"
+
urlParam
+
"(
\\
W|$)"
,
"g"
),
encodedVal
+
"$1"
);
url
=
url
.
replace
(
new
RegExp
(
":"
+
urlParam
+
"(
\\
W|$)"
,
"g"
),
function
(
match
,
p1
)
{
return
encodedVal
+
p1
;
});
}
else
{
url
=
url
.
replace
(
new
RegExp
(
"(
\
/?):"
+
urlParam
+
"(
\\
W|$)"
,
"g"
),
function
(
match
,
leadingSlashes
,
tail
)
{
...
...
@@ -377,7 +408,7 @@ angular.module('ngResource', ['ng']).
});
// strip trailing slashes and set the url
url
=
url
.
replace
(
/
\/
+$/
,
''
);
url
=
url
.
replace
(
/
\/
+$/
,
''
)
||
'/'
;
// then replace collapse `/.` if found in the last URL path segment before the query
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
url
=
url
.
replace
(
/
\/\.(?=\w
+
(
$|
\?))
/
,
'.'
);
...
...
@@ -396,7 +427,7 @@ angular.module('ngResource', ['ng']).
};
function
R
esourceFactory
(
url
,
paramDefaults
,
actions
)
{
function
r
esourceFactory
(
url
,
paramDefaults
,
actions
)
{
var
route
=
new
Route
(
url
);
actions
=
extend
({},
DEFAULT_ACTIONS
,
actions
);
...
...
@@ -406,7 +437,8 @@ angular.module('ngResource', ['ng']).
actionParams
=
extend
({},
paramDefaults
,
actionParams
);
forEach
(
actionParams
,
function
(
value
,
key
){
if
(
isFunction
(
value
))
{
value
=
value
();
}
ids
[
key
]
=
value
&&
value
.
charAt
&&
value
.
charAt
(
0
)
==
'@'
?
getter
(
data
,
value
.
substr
(
1
))
:
value
;
ids
[
key
]
=
value
&&
value
.
charAt
&&
value
.
charAt
(
0
)
==
'@'
?
lookupDottedPath
(
data
,
value
.
substr
(
1
))
:
value
;
});
return
ids
;
}
...
...
@@ -416,7 +448,7 @@ angular.module('ngResource', ['ng']).
}
function
Resource
(
value
){
c
opy
(
value
||
{},
this
);
shallowClearAndC
opy
(
value
||
{},
this
);
}
forEach
(
actions
,
function
(
action
,
name
)
{
...
...
@@ -425,6 +457,7 @@ angular.module('ngResource', ['ng']).
Resource
[
name
]
=
function
(
a1
,
a2
,
a3
,
a4
)
{
var
params
=
{},
data
,
success
,
error
;
/* jshint -W086 */
/* (purposefully fall through case statements) */
switch
(
arguments
.
length
)
{
case
4
:
error
=
a4
;
...
...
@@ -456,14 +489,18 @@ angular.module('ngResource', ['ng']).
case
0
:
break
;
default
:
throw
$resourceMinErr
(
'badargs'
,
"Expected up to 4 arguments [params, data, success, error], got {0} arguments"
,
arguments
.
length
);
"Expected up to 4 arguments [params, data, success, error], got {0} arguments"
,
arguments
.
length
);
}
/* jshint +W086 */
/* (purposefully fall through case statements) */
var
isInstanceCall
=
data
instanceof
Resource
;
var
isInstanceCall
=
this
instanceof
Resource
;
var
value
=
isInstanceCall
?
data
:
(
action
.
isArray
?
[]
:
new
Resource
(
data
));
var
httpConfig
=
{};
var
responseInterceptor
=
action
.
interceptor
&&
action
.
interceptor
.
response
||
defaultResponseInterceptor
;
var
responseErrorInterceptor
=
action
.
interceptor
&&
action
.
interceptor
.
responseError
||
undefined
;
var
responseInterceptor
=
action
.
interceptor
&&
action
.
interceptor
.
response
||
defaultResponseInterceptor
;
var
responseErrorInterceptor
=
action
.
interceptor
&&
action
.
interceptor
.
responseError
||
undefined
;
forEach
(
action
,
function
(
value
,
key
)
{
if
(
key
!=
'params'
&&
key
!=
'isArray'
&&
key
!=
'interceptor'
)
{
...
...
@@ -471,34 +508,37 @@ angular.module('ngResource', ['ng']).
}
});
httpConfig
.
data
=
data
;
route
.
setUrlParams
(
httpConfig
,
extend
({},
extractParams
(
data
,
action
.
params
||
{}),
params
),
action
.
url
);
if
(
hasBody
)
httpConfig
.
data
=
data
;
route
.
setUrlParams
(
httpConfig
,
extend
({},
extractParams
(
data
,
action
.
params
||
{}),
params
),
action
.
url
);
var
promise
=
$http
(
httpConfig
).
then
(
function
(
response
)
{
var
data
=
response
.
data
,
promise
=
value
.
$promise
;
if
(
data
)
{
if
(
angular
.
isArray
(
data
)
!=
!!
action
.
isArray
)
{
throw
$resourceMinErr
(
'badcfg'
,
'Error in resource configuration. Expected response'
+
' to contain an {0} but got an {1}'
,
// Need to convert action.isArray to boolean in case it is undefined
// jshint -W018
if
(
angular
.
isArray
(
data
)
!==
(
!!
action
.
isArray
))
{
throw
$resourceMinErr
(
'badcfg'
,
'Error in resource configuration. Expected '
+
'response to contain an {0} but got an {1}'
,
action
.
isArray
?
'array'
:
'object'
,
angular
.
isArray
(
data
)?
'array'
:
'object'
);
}
// jshint +W018
if
(
action
.
isArray
)
{
value
.
length
=
0
;
forEach
(
data
,
function
(
item
)
{
value
.
push
(
new
Resource
(
item
));
});
}
else
{
c
opy
(
data
,
value
);
shallowClearAndC
opy
(
data
,
value
);
value
.
$promise
=
promise
;
}
}
value
.
$resolved
=
true
;
(
success
||
noop
)(
value
,
response
.
headers
);
response
.
resource
=
value
;
return
response
;
...
...
@@ -508,8 +548,15 @@ angular.module('ngResource', ['ng']).
(
error
||
noop
)(
response
);
return
$q
.
reject
(
response
);
})
.
then
(
responseInterceptor
,
responseErrorInterceptor
)
;
});
promise
=
promise
.
then
(
function
(
response
)
{
var
value
=
responseInterceptor
(
response
);
(
success
||
noop
)(
value
,
response
.
headers
);
return
value
;
},
responseErrorInterceptor
);
if
(
!
isInstanceCall
)
{
// we are creating instance / collection
...
...
@@ -530,19 +577,19 @@ angular.module('ngResource', ['ng']).
if
(
isFunction
(
params
))
{
error
=
success
;
success
=
params
;
params
=
{};
}
var
result
=
Resource
[
name
]
(
params
,
this
,
success
,
error
);
var
result
=
Resource
[
name
]
.
call
(
this
,
params
,
this
,
success
,
error
);
return
result
.
$promise
||
result
;
};
});
Resource
.
bind
=
function
(
additionalParamDefaults
){
return
R
esourceFactory
(
url
,
extend
({},
paramDefaults
,
additionalParamDefaults
),
actions
);
return
r
esourceFactory
(
url
,
extend
({},
paramDefaults
,
additionalParamDefaults
),
actions
);
};
return
Resource
;
}
return
R
esourceFactory
;
return
r
esourceFactory
;
}]);
...
...
h/lib/angular-route.js
View file @
b171c1bc
/**
* @license AngularJS v1.2.
0-rc.2
* (c) 2010-201
2
Google, Inc. http://angularjs.org
* @license AngularJS v1.2.
13
* (c) 2010-201
4
Google, Inc. http://angularjs.org
* License: MIT
*/
(
function
(
window
,
angular
,
undefined
)
{
'use strict'
;
var
copy
=
angular
.
copy
,
equals
=
angular
.
equals
,
extend
=
angular
.
extend
,
forEach
=
angular
.
forEach
,
isDefined
=
angular
.
isDefined
,
isFunction
=
angular
.
isFunction
,
isString
=
angular
.
isString
,
jqLite
=
angular
.
element
,
noop
=
angular
.
noop
,
toJson
=
angular
.
toJson
;
function
inherit
(
parent
,
extra
)
{
return
extend
(
new
(
extend
(
function
()
{},
{
prototype
:
parent
}))(),
extra
);
}
/**
* @ngdoc overview
* @name ngRoute
...
...
@@ -30,10 +14,14 @@ function inherit(parent, extra) {
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* {@installModule route}
*
* <div doc-module-components="ngRoute"></div>
*/
/* global -ngRouteModule */
var
ngRouteModule
=
angular
.
module
(
'ngRoute'
,
[
'ng'
]).
provider
(
'$route'
,
$RouteProvider
);
...
...
@@ -44,11 +32,19 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*
* @description
*
* Used for configuring routes. See {@link ngRoute.$route $route} for an example.
* Used for configuring routes.
*
* ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
*
* ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*/
function
$RouteProvider
(){
function
inherit
(
parent
,
extra
)
{
return
angular
.
extend
(
new
(
angular
.
extend
(
function
()
{},
{
prototype
:
parent
}))(),
extra
);
}
var
routes
=
{};
/**
...
...
@@ -61,13 +57,13 @@ function $RouteProvider(){
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
* route definition.
*
* * `path` can contain named groups starting with a colon
(`:name`)
. All characters up
* * `path` can contain named groups starting with a colon
: e.g. `:name`
. All characters up
* to the next slash are matched and stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain named groups starting with a colon and ending with a star
(`:name*`).
* All characters are eagerly stored in `$routeParams` under the given `name`
* * `path` can contain named groups starting with a colon and ending with a star
:
*
e.g.`:name*`.
All characters are eagerly stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain optional named groups with a question mark
(`:name?`)
.
* * `path` can contain optional named groups with a question mark
: e.g.`:name?`
.
*
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
* `/color/brown/largecode/code/with/slashs/edit` and extract:
...
...
@@ -81,9 +77,9 @@ function $RouteProvider(){
*
* Object properties:
*
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
newly
*
created scope or the name of a {@link angular.Module#controller registered controller}
* if passed as a string.
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
*
newly created scope or the name of a {@link angular.Module#controller registered
*
controller}
if passed as a string.
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be
* published to scope under the `controllerAs` name.
* - `template` – `{string=|function()=}` – html template as a string or a function that
...
...
@@ -105,17 +101,22 @@ function $RouteProvider(){
* `$location.path()` by applying the current route
*
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, they will be
* resolved and converted to a value before the controller is instantiated and the
* `$routeChangeSuccess` event is fired. The map object is:
* be injected into the controller. If any of these dependencies are promises, the router
* will wait for them all to be resolved or one to be rejected before the controller is
* instantiated.
* If all the promises are resolved successfully, the values of the resolved promises are
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
* fired. If any of the promises are rejected the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
* is:
*
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
* and the return value is treated as the dependency. If the result is a promise, it is
resolved
*
before its value is injected into the controller. Be aware that `ngRoute.$routeParams` will
*
still refer to the previous route within these resolve functions. Use `$route.current.params`
* to access the new route parameters, instead.
* and the return value is treated as the dependency. If the result is a promise, it is
*
resolved before its value is injected into the controller. Be aware that
*
`ngRoute.$routeParams` will still refer to the previous route within these resolve
*
functions. Use `$route.current.params`
to access the new route parameters, instead.
*
* - `redirectTo` – {(string|function())=} – value to update
* {@link ng.$location $location} path with and trigger route redirection.
...
...
@@ -130,8 +131,8 @@ function $RouteProvider(){
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only
$location.search()
* changes.
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only
`$location.search()`
*
or `$location.hash()`
changes.
*
* If the option is set to `false` and url in the browser changes, then
* `$routeUpdate` event is broadcasted on the root scope.
...
...
@@ -147,7 +148,7 @@ function $RouteProvider(){
* Adds a new route definition to the `$route` service.
*/
this
.
when
=
function
(
path
,
route
)
{
routes
[
path
]
=
extend
(
routes
[
path
]
=
angular
.
extend
(
{
reloadOnSearch
:
true
},
route
,
path
&&
pathRegExp
(
path
,
route
)
...
...
@@ -159,7 +160,7 @@ function $RouteProvider(){
?
path
.
substr
(
0
,
path
.
length
-
1
)
:
path
+
'/'
;
routes
[
redirectPath
]
=
extend
(
routes
[
redirectPath
]
=
angular
.
extend
(
{
redirectTo
:
path
},
pathRegExp
(
redirectPath
,
route
)
);
...
...
@@ -189,7 +190,7 @@ function $RouteProvider(){
path
=
path
.
replace
(
/
([
().
])
/g
,
'
\\
$1'
)
.
replace
(
/
(\/)?
:
(\w
+
)([\?
|
\*])?
/g
,
function
(
_
,
slash
,
key
,
option
){
.
replace
(
/
(\/)?
:
(\w
+
)([\?\*])?
/g
,
function
(
_
,
slash
,
key
,
option
){
var
optional
=
option
===
'?'
?
option
:
null
;
var
star
=
option
===
'*'
?
option
:
null
;
keys
.
push
({
name
:
key
,
optional
:
!!
optional
});
...
...
@@ -198,7 +199,9 @@ function $RouteProvider(){
+
(
optional
?
''
:
slash
)
+
'(?:'
+
(
optional
?
slash
:
''
)
+
(
star
&&
'(.+)?'
||
'([^/]+)?'
)
+
')'
+
(
star
&&
'(.+?)'
||
'([^/]+)'
)
+
(
optional
||
''
)
+
')'
+
(
optional
||
''
);
})
.
replace
(
/
([\/
$
\*])
/g
,
'
\\
$1'
);
...
...
@@ -225,8 +228,15 @@ function $RouteProvider(){
};
this
.
$get
=
[
'$rootScope'
,
'$location'
,
'$routeParams'
,
'$q'
,
'$injector'
,
'$http'
,
'$templateCache'
,
'$sce'
,
function
(
$rootScope
,
$location
,
$routeParams
,
$q
,
$injector
,
$http
,
$templateCache
,
$sce
)
{
this
.
$get
=
[
'$rootScope'
,
'$location'
,
'$routeParams'
,
'$q'
,
'$injector'
,
'$http'
,
'$templateCache'
,
'$sce'
,
function
(
$rootScope
,
$location
,
$routeParams
,
$q
,
$injector
,
$http
,
$templateCache
,
$sce
)
{
/**
* @ngdoc object
...
...
@@ -255,8 +265,9 @@ function $RouteProvider(){
*
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
*
* The `$route` service is typically used in conjunction with the {@link ngRoute.directive:ngView `ngView`}
* directive and the {@link ngRoute.$routeParams `$routeParams`} service.
* The `$route` service is typically used in conjunction with the
* {@link ngRoute.directive:ngView `ngView`} directive and the
* {@link ngRoute.$routeParams `$routeParams`} service.
*
* @example
This example shows how changing the URL hash causes the `$route` to match a route against the
...
...
@@ -265,7 +276,7 @@ function $RouteProvider(){
Note that this example is using {@link ng.directive:script inlined templates}
to get it working on jsfiddle as well.
<example module="ngView" deps="angular-route.js">
<example module="ngView
Example
" deps="angular-route.js">
<file name="index.html">
<div ng-controller="MainCntl">
Choose:
...
...
@@ -298,7 +309,9 @@ function $RouteProvider(){
</file>
<file name="script.js">
angular.module('ngView', ['ngRoute']).config(function($routeProvider, $locationProvider) {
angular.module('ngViewExample', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
...
...
@@ -337,17 +350,17 @@ function $RouteProvider(){
}
</file>
<file name="
scenario
.js">
<file name="
protractorTest
.js">
it('should load and compile correct template', function() {
element(
'a:contains("Moby: Ch1")'
).click();
var content = element(
'.doc-example-live [ng-view]').t
ext();
element(
by.linkText('Moby: Ch1')
).click();
var content = element(
by.css('.doc-example-live [ng-view]')).getT
ext();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element(
'a:contains("Scarlet")'
).click();
sleep(2); // promises are not part of scenario waiting
content = element(
'.doc-example-live [ng-view]').t
ext();
element(
by.partialLinkText('Scarlet')
).click();
content = element(
by.css('.doc-example-live [ng-view]')).getT
ext();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
...
...
@@ -362,11 +375,12 @@ function $RouteProvider(){
* @eventType broadcast on root scope
* @description
* Broadcasted before a route change. At this point the route services starts
* resolving all of the dependencies needed for the route change to occur
s
.
* resolving all of the dependencies needed for the route change to occur.
* Typically this involves fetching the view template as well as any dependencies
* defined in `resolve` route property. Once all of the dependencies are resolved
* `$routeChangeSuccess` is fired.
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} next Future route information.
* @param {Route} current Current route information.
*/
...
...
@@ -383,7 +397,8 @@ function $RouteProvider(){
*
* @param {Object} angularEvent Synthetic event object.
* @param {Route} current Current route information.
* @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered.
* @param {Route|Undefined} previous Previous route information, or undefined if current is
* first route entered.
*/
/**
...
...
@@ -394,6 +409,7 @@ function $RouteProvider(){
* @description
* Broadcasted if any of the resolve promises are rejected.
*
* @param {Object} angularEvent Synthetic event object
* @param {Route} current Current route information.
* @param {Route} previous Previous route information.
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
...
...
@@ -477,9 +493,10 @@ function $RouteProvider(){
last
=
$route
.
current
;
if
(
next
&&
last
&&
next
.
$$route
===
last
.
$$route
&&
equals
(
next
.
pathParams
,
last
.
pathParams
)
&&
!
next
.
reloadOnSearch
&&
!
forceReload
)
{
&&
angular
.
equals
(
next
.
pathParams
,
last
.
pathParams
)
&&
!
next
.
reloadOnSearch
&&
!
forceReload
)
{
last
.
params
=
next
.
params
;
copy
(
last
.
params
,
$routeParams
);
angular
.
copy
(
last
.
params
,
$routeParams
);
$rootScope
.
$broadcast
(
'$routeUpdate'
,
last
);
}
else
if
(
next
||
last
)
{
forceReload
=
false
;
...
...
@@ -487,7 +504,7 @@ function $RouteProvider(){
$route
.
current
=
next
;
if
(
next
)
{
if
(
next
.
redirectTo
)
{
if
(
isString
(
next
.
redirectTo
))
{
if
(
angular
.
isString
(
next
.
redirectTo
))
{
$location
.
path
(
interpolate
(
next
.
redirectTo
,
next
.
params
)).
search
(
next
.
params
)
.
replace
();
}
else
{
...
...
@@ -500,29 +517,30 @@ function $RouteProvider(){
$q
.
when
(
next
).
then
(
function
()
{
if
(
next
)
{
var
locals
=
extend
({},
next
.
resolve
),
var
locals
=
angular
.
extend
({},
next
.
resolve
),
template
,
templateUrl
;
forEach
(
locals
,
function
(
value
,
key
)
{
locals
[
key
]
=
isString
(
value
)
?
$injector
.
get
(
value
)
:
$injector
.
invoke
(
value
);
angular
.
forEach
(
locals
,
function
(
value
,
key
)
{
locals
[
key
]
=
angular
.
isString
(
value
)
?
$injector
.
get
(
value
)
:
$injector
.
invoke
(
value
);
});
if
(
isDefined
(
template
=
next
.
template
))
{
if
(
isFunction
(
template
))
{
if
(
angular
.
isDefined
(
template
=
next
.
template
))
{
if
(
angular
.
isFunction
(
template
))
{
template
=
template
(
next
.
params
);
}
}
else
if
(
isDefined
(
templateUrl
=
next
.
templateUrl
))
{
if
(
isFunction
(
templateUrl
))
{
}
else
if
(
angular
.
isDefined
(
templateUrl
=
next
.
templateUrl
))
{
if
(
angular
.
isFunction
(
templateUrl
))
{
templateUrl
=
templateUrl
(
next
.
params
);
}
templateUrl
=
$sce
.
getTrustedResourceUrl
(
templateUrl
);
if
(
isDefined
(
templateUrl
))
{
if
(
angular
.
isDefined
(
templateUrl
))
{
next
.
loadedTemplateUrl
=
templateUrl
;
template
=
$http
.
get
(
templateUrl
,
{
cache
:
$templateCache
}).
then
(
function
(
response
)
{
return
response
.
data
;
});
}
}
if
(
isDefined
(
template
))
{
if
(
angular
.
isDefined
(
template
))
{
locals
[
'$template'
]
=
template
;
}
return
$q
.
all
(
locals
);
...
...
@@ -533,7 +551,7 @@ function $RouteProvider(){
if
(
next
==
$route
.
current
)
{
if
(
next
)
{
next
.
locals
=
locals
;
copy
(
next
.
params
,
$routeParams
);
angular
.
copy
(
next
.
params
,
$routeParams
);
}
$rootScope
.
$broadcast
(
'$routeChangeSuccess'
,
next
,
last
);
}
...
...
@@ -552,10 +570,10 @@ function $RouteProvider(){
function
parseRoute
()
{
// Match a route
var
params
,
match
;
forEach
(
routes
,
function
(
route
,
path
)
{
angular
.
forEach
(
routes
,
function
(
route
,
path
)
{
if
(
!
match
&&
(
params
=
switchRouteMatcher
(
$location
.
path
(),
route
)))
{
match
=
inherit
(
route
,
{
params
:
extend
({},
$location
.
search
(),
params
),
params
:
angular
.
extend
({},
$location
.
search
(),
params
),
pathParams
:
params
});
match
.
$$route
=
route
;
}
...
...
@@ -569,7 +587,7 @@ function $RouteProvider(){
*/
function
interpolate
(
string
,
params
)
{
var
result
=
[];
forEach
((
string
||
''
).
split
(
':'
),
function
(
segment
,
i
)
{
angular
.
forEach
((
string
||
''
).
split
(
':'
),
function
(
segment
,
i
)
{
if
(
i
===
0
)
{
result
.
push
(
segment
);
}
else
{
...
...
@@ -599,7 +617,7 @@ ngRouteModule.provider('$routeParams', $RouteParamsProvider);
* Requires the {@link ngRoute `ngRoute`} module to be installed.
*
* The route parameters are a combination of {@link ng.$location `$location`}'s
* {@link ng.$location#
search `search()`} and {@link ng.$location#
path `path()`}.
* {@link ng.$location#
methods_search `search()`} and {@link ng.$location#methods_
path `path()`}.
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
*
* In case of parameter name collision, `path` params take precedence over `search` params.
...
...
@@ -626,6 +644,8 @@ function $RouteParamsProvider() {
}
ngRouteModule
.
directive
(
'ngView'
,
ngViewFactory
);
ngRouteModule
.
directive
(
'ngView'
,
ngViewFillContentFactory
);
/**
* @ngdoc directive
...
...
@@ -648,6 +668,16 @@ ngRouteModule.directive('ngView', ngViewFactory);
* The enter and leave animation occur concurrently.
*
* @scope
* @priority 400
* @param {string=} onload Expression to evaluate whenever the view updates.
*
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
* $anchorScroll} to scroll the viewport after the view is updated.
*
* - If the attribute is not set, disable scrolling.
* - If the attribute is set without value, enable scrolling.
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
* as an expression yields a truthy value.
* @example
<example module="ngViewExample" deps="angular-route.js" animations="true">
<file name="index.html">
...
...
@@ -659,8 +689,8 @@ ngRouteModule.directive('ngView', ngViewFactory);
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
<a href="Book/Scarlet">Scarlet Letter</a><br/>
<div class="
example
-animate-container">
<div ng-view class="view-
exampl
e"></div>
<div class="
view
-animate-container">
<div ng-view class="view-
animat
e"></div>
</div>
<hr />
...
...
@@ -688,7 +718,9 @@ ngRouteModule.directive('ngView', ngViewFactory);
</file>
<file name="animations.css">
.example-animate-container {
.view-animate-container {
position:relative;
height:100px!important;
position:relative;
background:white;
border:1px solid black;
...
...
@@ -696,14 +728,12 @@ ngRouteModule.directive('ngView', ngViewFactory);
overflow:hidden;
}
.
example-animate-container > div
{
.
view-animate
{
padding:10px;
}
.view-
example.ng-enter, .view-exampl
e.ng-leave {
.view-
animate.ng-enter, .view-animat
e.ng-leave {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
display:block;
...
...
@@ -718,26 +748,20 @@ ngRouteModule.directive('ngView', ngViewFactory);
padding:10px;
}
.example-animate-container {
position:relative;
height:100px;
}
.view-example.ng-enter {
.view-animate.ng-enter {
left:100%;
}
.view-
exampl
e.ng-enter.ng-enter-active {
.view-
animat
e.ng-enter.ng-enter-active {
left:0;
}
.view-example.ng-leave { }
.view-example.ng-leave.ng-leave-active {
.view-animate.ng-leave.ng-leave-active {
left:-100%;
}
</file>
<file name="script.js">
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], function($routeProvider, $locationProvider) {
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
function($routeProvider, $locationProvider) {
$routeProvider.when('/Book/:bookId', {
templateUrl: 'book.html',
controller: BookCntl,
...
...
@@ -770,16 +794,17 @@ ngRouteModule.directive('ngView', ngViewFactory);
}
</file>
<file name="
scenario
.js">
<file name="
protractorTest
.js">
it('should load and compile correct template', function() {
element(
'a:contains("Moby: Ch1")'
).click();
var content = element(
'.doc-example-live [ng-view]').t
ext();
element(
by.linkText('Moby: Ch1')
).click();
var content = element(
by.css('.doc-example-live [ng-view]')).getT
ext();
expect(content).toMatch(/controller\: ChapterCntl/);
expect(content).toMatch(/Book Id\: Moby/);
expect(content).toMatch(/Chapter Id\: 1/);
element('a:contains("Scarlet")').click();
content = element('.doc-example-live [ng-view]').text();
element(by.partialLinkText('Scarlet')).click();
content = element(by.css('.doc-example-live [ng-view]')).getText();
expect(content).toMatch(/controller\: BookCntl/);
expect(content).toMatch(/Book Id\: Scarlet/);
});
...
...
@@ -796,17 +821,17 @@ ngRouteModule.directive('ngView', ngViewFactory);
* @description
* Emitted every time the ngView content is reloaded.
*/
ngViewFactory
.
$inject
=
[
'$route'
,
'$anchorScroll'
,
'$
compile'
,
'$controller'
,
'$
animate'
];
function
ngViewFactory
(
$route
,
$anchorScroll
,
$
compile
,
$controller
,
$
animate
)
{
ngViewFactory
.
$inject
=
[
'$route'
,
'$anchorScroll'
,
'$animate'
];
function
ngViewFactory
(
$route
,
$anchorScroll
,
$animate
)
{
return
{
restrict
:
'ECA'
,
terminal
:
true
,
priority
:
10
00
,
priority
:
4
00
,
transclude
:
'element'
,
compile
:
function
(
element
,
attr
,
linker
)
{
return
function
(
scope
,
$element
,
attr
)
{
link
:
function
(
scope
,
$element
,
attr
,
ctrl
,
$transclude
)
{
var
currentScope
,
currentElement
,
autoScrollExp
=
attr
.
autoscroll
,
onloadExp
=
attr
.
onload
||
''
;
scope
.
$on
(
'$routeChangeSuccess'
,
update
);
...
...
@@ -827,42 +852,67 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
var
locals
=
$route
.
current
&&
$route
.
current
.
locals
,
template
=
locals
&&
locals
.
$template
;
if
(
template
)
{
if
(
angular
.
isDefined
(
template
)
)
{
var
newScope
=
scope
.
$new
();
linker
(
newScope
,
function
(
clone
)
{
var
current
=
$route
.
current
;
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
// However, using ng-view on an element with additional content does not make sense...
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var
clone
=
$transclude
(
newScope
,
function
(
clone
)
{
$animate
.
enter
(
clone
,
null
,
currentElement
||
$element
,
function
onNgViewEnter
()
{
if
(
angular
.
isDefined
(
autoScrollExp
)
&&
(
!
autoScrollExp
||
scope
.
$eval
(
autoScrollExp
)))
{
$anchorScroll
();
}
});
cleanupLastView
();
});
clone
.
html
(
template
);
$animate
.
enter
(
clone
,
null
,
$element
);
currentElement
=
clone
;
currentScope
=
current
.
scope
=
newScope
;
currentScope
.
$emit
(
'$viewContentLoaded'
);
currentScope
.
$eval
(
onloadExp
);
}
else
{
cleanupLastView
();
}
}
}
};
}
var
link
=
$compile
(
clone
.
contents
()),
current
=
$route
.
current
;
// This directive is called during the $transclude call of the first `ngView` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngView
// is called.
ngViewFillContentFactory
.
$inject
=
[
'$compile'
,
'$controller'
,
'$route'
];
function
ngViewFillContentFactory
(
$compile
,
$controller
,
$route
)
{
return
{
restrict
:
'ECA'
,
priority
:
-
400
,
link
:
function
(
scope
,
$element
)
{
var
current
=
$route
.
current
,
locals
=
current
.
locals
;
currentScope
=
current
.
scope
=
newScope
;
currentElement
=
clone
;
$element
.
html
(
locals
.
$template
);
var
link
=
$compile
(
$element
.
contents
());
if
(
current
.
controller
)
{
locals
.
$scope
=
currentS
cope
;
locals
.
$scope
=
s
cope
;
var
controller
=
$controller
(
current
.
controller
,
locals
);
if
(
current
.
controllerAs
)
{
currentS
cope
[
current
.
controllerAs
]
=
controller
;
s
cope
[
current
.
controllerAs
]
=
controller
;
}
clone
.
data
(
'$ngControllerController'
,
controller
);
clone
.
contents
().
data
(
'$ngControllerController'
,
controller
);
$element
.
data
(
'$ngControllerController'
,
controller
);
$element
.
children
().
data
(
'$ngControllerController'
,
controller
);
}
link
(
currentScope
);
currentScope
.
$emit
(
'$viewContentLoaded'
);
currentScope
.
$eval
(
onloadExp
);
// $anchorScroll might listen on event...
$anchorScroll
();
});
}
else
{
cleanupLastView
();
}
}
}
link
(
scope
);
}
};
}
...
...
h/lib/angular-sanitize.js
View file @
b171c1bc
/**
* @license AngularJS v1.2.
0-rc.2
* (c) 2010-201
2
Google, Inc. http://angularjs.org
* @license AngularJS v1.2.
13
* (c) 2010-201
4
Google, Inc. http://angularjs.org
* License: MIT
*/
(
function
(
window
,
angular
,
undefined
)
{
'use strict'
;
...
...
@@ -18,6 +18,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
*
* {@installModule sanitize}
*
* <div doc-module-components="ngSanitize"></div>
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/
...
...
@@ -49,6 +51,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
...
...
@@ -85,7 +89,10 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
<tr id="bind-html-with-trust">
<td>ng-bind-html</td>
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
<td><pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"><br/></div></pre></td>
<td>
<pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()">
</div></pre>
</td>
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
</tr>
<tr id="bind-default">
...
...
@@ -97,52 +104,71 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</table>
</div>
</doc:source>
<doc:scenario
>
<doc:protractor
>
it('should sanitize the html snippet by default', function() {
expect(using('#bind-html-with-sanitize').element('div').h
tml()).
expect(element(by.css('#bind-html-with-sanitize div')).getInnerH
tml()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should inline raw snippet if bound to a trusted value', function() {
expect(using('#bind-html-with-trust').element("div").h
tml()).
expect(element(by.css('#bind-html-with-trust div')).getInnerH
tml()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() {
expect(using('#bind-default').element('div').h
tml()).
expect(element(by.css('#bind-default div')).getInnerH
tml()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should update', function() {
input('snippet').enter('new <b onclick="alert(1)">text</b>');
expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
expect(using('#bind-html-with-trust').element('div').html()).toBe('new <b onclick="alert(1)">text</b>');
expect(using('#bind-default').element('div').html()).toBe("new <b onclick=\"alert(1)\">text</b>");
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('new <b>text</b>');
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
'new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
"new <b onclick=\"alert(1)\">text</b>");
});
</doc:scenario
>
</doc:protractor
>
</doc:example>
*/
var
$sanitize
=
function
(
html
)
{
function
$SanitizeProvider
()
{
this
.
$get
=
[
'$$sanitizeUri'
,
function
(
$$sanitizeUri
)
{
return
function
(
html
)
{
var
buf
=
[];
htmlParser
(
html
,
htmlSanitizeWriter
(
buf
));
htmlParser
(
html
,
htmlSanitizeWriter
(
buf
,
function
(
uri
,
isImage
)
{
return
!
/^unsafe/
.
test
(
$$sanitizeUri
(
uri
,
isImage
));
}));
return
buf
.
join
(
''
);
};
};
}];
}
function
sanitizeText
(
chars
)
{
var
buf
=
[];
var
writer
=
htmlSanitizeWriter
(
buf
,
angular
.
noop
);
writer
.
chars
(
chars
);
return
buf
.
join
(
''
);
}
// Regular Expressions for parsing tags and attributes
var
START_TAG_REGEXP
=
/^<
\s
*
([\w
:-
]
+
)((?:\s
+
[\w
:-
]
+
(?:\s
*=
\s
*
(?:(?:
"
[^
"
]
*"
)
|
(?:
'
[^
'
]
*'
)
|
[^
>
\s]
+
))?)
*
)\s
*
(\/?)\s
*>/
,
var
START_TAG_REGEXP
=
/^<
\s
*
([\w
:-
]
+
)((?:\s
+
[\w
:-
]
+
(?:\s
*=
\s
*
(?:(?:
"
[^
"
]
*"
)
|
(?:
'
[^
'
]
*'
)
|
[^
>
\s]
+
))?)
*
)\s
*
(\/?)\s
*>/
,
END_TAG_REGEXP
=
/^<
\s
*
\/\s
*
([\w
:-
]
+
)[^
>
]
*>/
,
ATTR_REGEXP
=
/
([\w
:-
]
+
)(?:\s
*=
\s
*
(?:(?:
"
((?:[^
"
])
*
)
"
)
|
(?:
'
((?:[^
'
])
*
)
'
)
|
([^
>
\s]
+
)))?
/g
,
BEGIN_TAG_REGEXP
=
/^</
,
BEGING_END_TAGE_REGEXP
=
/^<
\s
*
\/
/
,
COMMENT_REGEXP
=
/<!--
(
.*
?)
-->/g
,
DOCTYPE_REGEXP
=
/<!DOCTYPE
([^
>
]
*
?)
>/i
,
CDATA_REGEXP
=
/<!
\[
CDATA
\[(
.*
?)
]]>/g
,
URI_REGEXP
=
/^
((
ftp|https
?)
:
\/\/
|mailto:|tel:|#
)
/i
,
NON_ALPHANUMERIC_REGEXP
=
/
([^\#
-~| |!
])
/g
;
// Match everything outside of normal chars and " (quote character)
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP
=
/
([^\#
-~| |!
])
/g
;
// Good source of info about elements and attributes
...
...
@@ -157,23 +183,29 @@ var voidElements = makeMap("area,br,col,hr,img,wbr");
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var
optionalEndTagBlockElements
=
makeMap
(
"colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"
),
optionalEndTagInlineElements
=
makeMap
(
"rp,rt"
),
optionalEndTagElements
=
angular
.
extend
({},
optionalEndTagInlineElements
,
optionalEndTagBlockElements
);
optionalEndTagElements
=
angular
.
extend
({},
optionalEndTagInlineElements
,
optionalEndTagBlockElements
);
// Safe Block Elements - HTML5
var
blockElements
=
angular
.
extend
({},
optionalEndTagBlockElements
,
makeMap
(
"address,article,
aside,
"
+
"
blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6
,"
+
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"
));
var
blockElements
=
angular
.
extend
({},
optionalEndTagBlockElements
,
makeMap
(
"address,article,"
+
"
aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5
,"
+
"h
6,h
eader,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"
));
// Inline Elements - HTML5
var
inlineElements
=
angular
.
extend
({},
optionalEndTagInlineElements
,
makeMap
(
"a,abbr,acronym,b,
bdi,bdo,
"
+
"b
ig,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small
,"
+
"span,strike,strong,sub,sup,time,tt,u,var"
));
var
inlineElements
=
angular
.
extend
({},
optionalEndTagInlineElements
,
makeMap
(
"a,abbr,acronym,b,"
+
"b
di,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s
,"
+
"s
amp,small,s
pan,strike,strong,sub,sup,time,tt,u,var"
));
// Special Elements (can contain anything)
var
specialElements
=
makeMap
(
"script,style"
);
var
validElements
=
angular
.
extend
({},
voidElements
,
blockElements
,
inlineElements
,
optionalEndTagElements
);
var
validElements
=
angular
.
extend
({},
voidElements
,
blockElements
,
inlineElements
,
optionalEndTagElements
);
//Attributes that have href and hence need to be sanitized
var
uriAttrs
=
makeMap
(
"background,cite,href,longdesc,src,usemap"
);
...
...
@@ -181,7 +213,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'
+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'
+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'
+
'scope,scrolling,shape,span,start,summary,target,title,type,'
+
'scope,scrolling,shape,s
ize,s
pan,start,summary,target,title,type,'
+
'valign,value,vspace,width'
));
function
makeMap
(
str
)
{
...
...
@@ -215,14 +247,22 @@ function htmlParser( html, handler ) {
// Comment
if
(
html
.
indexOf
(
"<!--"
)
===
0
)
{
index
=
html
.
indexOf
(
"-->"
);
// comments containing -- are not allowed unless they terminate the comment
index
=
html
.
indexOf
(
"--"
,
4
);
if
(
index
>=
0
)
{
if
(
index
>=
0
&&
html
.
lastIndexOf
(
"-->"
,
index
)
===
index
)
{
if
(
handler
.
comment
)
handler
.
comment
(
html
.
substring
(
4
,
index
)
);
html
=
html
.
substring
(
index
+
3
);
chars
=
false
;
}
// DOCTYPE
}
else
if
(
DOCTYPE_REGEXP
.
test
(
html
)
)
{
match
=
html
.
match
(
DOCTYPE_REGEXP
);
if
(
match
)
{
html
=
html
.
replace
(
match
[
0
]
,
''
);
chars
=
false
;
}
// end tag
}
else
if
(
BEGING_END_TAGE_REGEXP
.
test
(
html
)
)
{
match
=
html
.
match
(
END_TAG_REGEXP
);
...
...
@@ -254,10 +294,9 @@ function htmlParser( html, handler ) {
}
}
else
{
html
=
html
.
replace
(
new
RegExp
(
"(.*)<
\\
s*
\\
/
\\
s*"
+
stack
.
last
()
+
"[^>]*>"
,
'i'
),
function
(
all
,
text
){
text
=
text
.
replace
(
COMMENT_REGEXP
,
"$1"
).
replace
(
CDATA_REGEXP
,
"$1"
);
html
=
html
.
replace
(
new
RegExp
(
"(.*)<
\\
s*
\\
/
\\
s*"
+
stack
.
last
()
+
"[^>]*>"
,
'i'
),
function
(
all
,
text
){
text
=
text
.
replace
(
COMMENT_REGEXP
,
"$1"
).
replace
(
CDATA_REGEXP
,
"$1"
);
if
(
handler
.
chars
)
handler
.
chars
(
decodeEntities
(
text
)
);
...
...
@@ -268,7 +307,8 @@ function htmlParser( html, handler ) {
}
if
(
html
==
last
)
{
throw
$sanitizeMinErr
(
'badparse'
,
"The sanitizer was unable to parse the following block of html: {0}"
,
html
);
throw
$sanitizeMinErr
(
'badparse'
,
"The sanitizer was unable to parse the following block "
+
"of html: {0}"
,
html
);
}
last
=
html
;
}
...
...
@@ -295,7 +335,8 @@ function htmlParser( html, handler ) {
var
attrs
=
{};
rest
.
replace
(
ATTR_REGEXP
,
function
(
match
,
name
,
doubleQuotedValue
,
singleQuotedValue
,
unquotedValue
)
{
rest
.
replace
(
ATTR_REGEXP
,
function
(
match
,
name
,
doubleQuotedValue
,
singleQuotedValue
,
unquotedValue
)
{
var
value
=
doubleQuotedValue
||
singleQuotedValue
||
unquotedValue
...
...
@@ -326,15 +367,32 @@ function htmlParser( html, handler ) {
}
}
var
hiddenPre
=
document
.
createElement
(
"pre"
);
var
spaceRe
=
/^
(\s
*
)([\s\S]
*
?)(\s
*
)
$/
;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var
hiddenPre
=
document
.
createElement
(
"pre"
);
function
decodeEntities
(
value
)
{
hiddenPre
.
innerHTML
=
value
.
replace
(
/</g
,
"<"
);
return
hiddenPre
.
innerText
||
hiddenPre
.
textContent
||
''
;
if
(
!
value
)
{
return
''
;
}
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var
parts
=
spaceRe
.
exec
(
value
);
var
spaceBefore
=
parts
[
1
];
var
spaceAfter
=
parts
[
3
];
var
content
=
parts
[
2
];
if
(
content
)
{
hiddenPre
.
innerHTML
=
content
.
replace
(
/</g
,
"<"
);
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content
=
'textContent'
in
hiddenPre
?
hiddenPre
.
textContent
:
hiddenPre
.
innerText
;
}
return
spaceBefore
+
content
+
spaceAfter
;
}
/**
...
...
@@ -364,7 +422,7 @@ function encodeEntities(value) {
* comment: function(text) {}
* }
*/
function
htmlSanitizeWriter
(
buf
){
function
htmlSanitizeWriter
(
buf
,
uriValidator
){
var
ignore
=
false
;
var
out
=
angular
.
bind
(
buf
,
buf
.
push
);
return
{
...
...
@@ -373,12 +431,14 @@ function htmlSanitizeWriter(buf){
if
(
!
ignore
&&
specialElements
[
tag
])
{
ignore
=
tag
;
}
if
(
!
ignore
&&
validElements
[
tag
]
==
true
)
{
if
(
!
ignore
&&
validElements
[
tag
]
==
=
true
)
{
out
(
'<'
);
out
(
tag
);
angular
.
forEach
(
attrs
,
function
(
value
,
key
){
var
lkey
=
angular
.
lowercase
(
key
);
if
(
validAttrs
[
lkey
]
==
true
&&
(
uriAttrs
[
lkey
]
!==
true
||
value
.
match
(
URI_REGEXP
)))
{
var
isImage
=
(
tag
===
'img'
&&
lkey
===
'src'
)
||
(
lkey
===
'background'
);
if
(
validAttrs
[
lkey
]
===
true
&&
(
uriAttrs
[
lkey
]
!==
true
||
uriValidator
(
value
,
isImage
)))
{
out
(
' '
);
out
(
key
);
out
(
'="'
);
...
...
@@ -391,7 +451,7 @@ function htmlSanitizeWriter(buf){
},
end
:
function
(
tag
){
tag
=
angular
.
lowercase
(
tag
);
if
(
!
ignore
&&
validElements
[
tag
]
==
true
)
{
if
(
!
ignore
&&
validElements
[
tag
]
==
=
true
)
{
out
(
'</'
);
out
(
tag
);
out
(
'>'
);
...
...
@@ -410,7 +470,9 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service
angular
.
module
(
'ngSanitize'
,
[]).
value
(
'$sanitize'
,
$sanitize
);
angular
.
module
(
'ngSanitize'
,
[]).
provider
(
'$sanitize'
,
$SanitizeProvider
);
/* global sanitizeText: false */
/**
* @ngdoc filter
...
...
@@ -477,41 +539,43 @@ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
</tr>
</table>
</doc:source>
<doc:
scenario
>
<doc:
protractor
>
it('should linkify the snippet with urls', function() {
expect(using('#linky-filter').binding('snippet | linky')).
toBe('Pretty text with some links: ' +
'<a href="http://angularjs.org/">http://angularjs.org/</a>, ' +
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>, ' +
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' +
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
});
it ('should not linkify snippet without the linky filter', function() {
expect(using('#escaped-html').binding('snippet')).
toBe("Pretty text with some links:\n" +
"http://angularjs.org/,\n" +
"mailto:us@somewhere.org,\n" +
"another@somewhere.org,\n" +
"and one more: ftp://127.0.0.1/.");
it('should not linkify snippet without the linky filter', function() {
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
});
it('should update', function() {
input('snippet').enter('new http://link.');
expect(using('#linky-filter').binding('snippet | linky')).
toBe('new <a href="http://link">http://link</a>.');
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new http://link.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('new http://link.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
.toBe('new http://link.');
});
it('should work with the target property', function() {
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});
</doc:
scenario
>
</doc:
protractor
>
</doc:example>
*/
angular
.
module
(
'ngSanitize'
).
filter
(
'linky'
,
function
()
{
var
LINKY_URL_REGEXP
=
/
((
ftp|https
?)
:
\/\/
|
(
mailto:
)?[
A-Za-z0-9._%+-
]
+@
)\S
*
[^\s\.\;\,\(\)\{\}\<\>]
/
,
angular
.
module
(
'ngSanitize'
).
filter
(
'linky'
,
[
'$sanitize'
,
function
(
$sanitize
)
{
var
LINKY_URL_REGEXP
=
/
((
ftp|https
?)
:
\/\/
|
(
mailto:
)?[
A-Za-z0-9._%+-
]
+@
)\S
*
[^\s
.;,(){}<>
]
/
,
MAILTO_REGEXP
=
/^mailto:/
;
return
function
(
text
,
target
)
{
...
...
@@ -519,31 +583,43 @@ angular.module('ngSanitize').filter('linky', function() {
var
match
;
var
raw
=
text
;
var
html
=
[];
// TODO(vojta): use $sanitize instead
var
writer
=
htmlSanitizeWriter
(
html
);
var
url
;
var
i
;
var
properties
=
{};
if
(
angular
.
isDefined
(
target
))
{
properties
.
target
=
target
;
}
while
((
match
=
raw
.
match
(
LINKY_URL_REGEXP
)))
{
// We can not end in these as they are sometimes found at the end of the sentence
url
=
match
[
0
];
// if we did not match ftp/http/mailto then assume mailto
if
(
match
[
2
]
==
match
[
3
])
url
=
'mailto:'
+
url
;
i
=
match
.
index
;
writer
.
chars
(
raw
.
substr
(
0
,
i
));
properties
.
href
=
url
;
writer
.
start
(
'a'
,
properties
);
writer
.
chars
(
match
[
0
].
replace
(
MAILTO_REGEXP
,
''
));
writer
.
end
(
'a'
);
addText
(
raw
.
substr
(
0
,
i
));
addLink
(
url
,
match
[
0
].
replace
(
MAILTO_REGEXP
,
''
));
raw
=
raw
.
substring
(
i
+
match
[
0
].
length
);
}
writer
.
chars
(
raw
);
return
html
.
join
(
''
);
addText
(
raw
);
return
$sanitize
(
html
.
join
(
''
));
function
addText
(
text
)
{
if
(
!
text
)
{
return
;
}
html
.
push
(
sanitizeText
(
text
));
}
function
addLink
(
url
,
text
)
{
html
.
push
(
'<a '
);
if
(
angular
.
isDefined
(
target
))
{
html
.
push
(
'target="'
);
html
.
push
(
target
);
html
.
push
(
'" '
);
}
html
.
push
(
'href="'
);
html
.
push
(
url
);
html
.
push
(
'">'
);
addText
(
text
);
html
.
push
(
'</a>'
);
}
};
});
}
]
);
})(
window
,
window
.
angular
);
h/lib/angular.js
View file @
b171c1bc
This source diff could not be displayed because it is too large. You can
view the blob
instead.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment